Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

ZoomInfo

Connect FlutterFlow to ZoomInfo via a Firebase Cloud Function proxy: ZoomInfo's PKI-to-JWT authentication (username, client ID, and private key) cannot run safely in compiled Dart, so the token exchange and Enrich API calls live entirely in the function. FlutterFlow sends a domain or email variable; the function returns enriched company and contact data. Note that ZoomInfo is enterprise-only with annual contracts — no self-serve API keys.

What you'll learn

  • How ZoomInfo's PKI-to-JWT authentication works and why it must run in a Cloud Function, not Dart
  • How to deploy a Firebase Cloud Function that exchanges credentials for a JWT and calls the ZoomInfo Enrich API
  • How to configure a FlutterFlow API Call to hit your Cloud Function with a domain or email variable
  • How to parse ZoomInfo's company and contact response with JSON Paths and bind results to form fields or a detail card
  • How to debounce enrichment calls to protect your per-record credit cap from being consumed on every keystroke
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced16 min read60 minutesCommunicationLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to ZoomInfo via a Firebase Cloud Function proxy: ZoomInfo's PKI-to-JWT authentication (username, client ID, and private key) cannot run safely in compiled Dart, so the token exchange and Enrich API calls live entirely in the function. FlutterFlow sends a domain or email variable; the function returns enriched company and contact data. Note that ZoomInfo is enterprise-only with annual contracts — no self-serve API keys.

Quick facts about this guide
FactValue
ToolZoomInfo
CategoryCommunication
MethodFlutterFlow API Call
DifficultyAdvanced
Time required60 minutes
Last updatedJuly 2026

Connecting FlutterFlow to ZoomInfo: Lead Enrichment Without Leaking Enterprise Credentials

Despite appearing in the Communication category, ZoomInfo is a B2B sales-intelligence platform, not a chat or calling tool. Its primary use-case in a FlutterFlow app is lead enrichment: a user types a company domain or business email address into a form, and the app returns firmographic and contact data — company name, industry, headcount, revenue range, contact title, LinkedIn URL — without the user having to fill it in manually. This dramatically improves lead capture quality in sales or CRM-style mobile apps.

The integration challenge is ZoomInfo's authentication model. ZoomInfo uses a PKI-based flow: a username, client ID, and private key are combined to generate a short-lived JWT, then that JWT is sent as a Bearer token to the Enrich API at api.zoominfo.com. Putting the private key in Dart code ships it inside the compiled app binary, where it can be extracted. The entire authentication and enrichment call chain must therefore run in a Firebase Cloud Function, which FlutterFlow calls with just a domain or email variable.

ZoomInfo is enterprise-only with annual contracts (commonly $15K+/year, sales-gated, no self-serve tier — verify with your account rep). Credit usage is metered per record, meaning every API call consumes your contract's credit balance. This makes debouncing critical: triggering enrichment on every keystroke while the user types a domain would burn through your credit cap extremely quickly. Call the API only on form submission, not on input changes.

Integration method

FlutterFlow API Call

FlutterFlow connects to ZoomInfo through a Firebase Cloud Function proxy. ZoomInfo's authentication is a two-step PKI flow: a username, client ID, and private key are exchanged for a short-lived JWT Bearer token, then that token is used to call the Enrich or Search API at api.zoominfo.com. Both the private key and the JWT must stay server-side — a Cloud Function handles the exchange and proxies all enrichment calls, returning only the company and contact data to FlutterFlow. FlutterFlow API Calls hit your function with a domain or email variable and parse the response with JSON Paths bound to form fields or detail cards.

Prerequisites

  • A FlutterFlow project open in the browser at app.flutterflow.io
  • An active ZoomInfo enterprise contract with API access provisioned (contact ZoomInfo sales if you do not have one)
  • Your ZoomInfo API credentials: username, client ID, and private key (provided by ZoomInfo after contract activation)
  • A Firebase project on the Blaze plan (required for Cloud Functions with outbound HTTP calls)
  • Familiarity with Firebase Cloud Functions deployment — you will write and deploy a Node.js function

Step-by-step guide

1

Step 1: Understand ZoomInfo's authentication flow and why it belongs in a Cloud Function

Before writing any FlutterFlow code, it is important to understand why this integration requires a server-side proxy and what ZoomInfo's authentication actually involves — this will save you from a frustrating debugging loop later. ZoomInfo's API authentication is a JWT-based PKI flow. Your ZoomInfo account comes with three credentials: a username (your ZoomInfo login email), a client ID (a string identifier for your API app), and a private key (a cryptographic secret). To make API calls, you: 1. Combine the username and client ID to create a payload. 2. Sign the payload with the private key to produce a JWT. 3. Send the JWT to ZoomInfo's auth endpoint to receive a short-lived Bearer access token. 4. Use the Bearer token in the Authorization header of Enrich/Search API calls. Verify the exact format of the JWT payload and signing algorithm with your ZoomInfo account representative — the specifics can differ between contract tiers. The private key step is the critical security constraint: if you put the private key in Dart code, it ships inside the compiled Flutter app binary. Anyone who downloads your app and uses standard reverse-engineering tools can extract the private key and use it to call ZoomInfo's API as you — consuming your per-record credit balance and exposing your enterprise data contract. The correct architecture: the private key never leaves your Firebase Cloud Function. FlutterFlow sends only a domain or email to your function; the function does the JWT generation and Enrich call; the function returns only the enriched data. ZoomInfo never sees a direct call from the FlutterFlow app.

Pro tip: Contact your ZoomInfo account representative to confirm the exact JWT payload structure (claims, algorithm, expiry) for your contract tier before writing the Cloud Function. The flow described here is the standard pattern, but ZoomInfo's auth details vary by plan.

Expected result: You have a clear understanding of the three-credential JWT flow and have confirmed your ZoomInfo username, client ID, and private key are available from your ZoomInfo account admin panel.

2

Step 2: Deploy a Firebase Cloud Function that handles JWT generation and Enrich API calls

Create a Firebase Cloud Function in your Firebase project (Blaze plan required for outbound HTTP). This function is responsible for: 1. Reading the ZoomInfo credentials from Firebase environment config — never from source code. 2. Generating a JWT using the private key (using the jsonwebtoken npm package). 3. Exchanging the JWT for a short-lived Bearer access token at ZoomInfo's token endpoint. 4. Calling the ZoomInfo Enrich API at api.zoominfo.com with the Bearer token and the domain or email passed by FlutterFlow. 5. Returning the enriched company and contact data as JSON. Before deploying, set environment config: firebase functions:config:set zoominfo.username="your@email.com" zoominfo.client_id="YOUR_CLIENT_ID" zoominfo.private_key="YOUR_PRIVATE_KEY" The private_key value should be the PEM-formatted key string with newlines escaped. Deploy with firebase deploy --only functions. The Cloud Function also handles caching of the access token in memory to avoid re-generating the JWT on every enrichment call, since JWT generation is computationally more expensive than a cached Bearer token lookup. IMPORTANT: ZoomInfo bills per record enriched. Add logic in the function to validate that the input (domain or email) is non-empty and well-formed before calling the Enrich API — reject invalid inputs at the function level rather than burning a credit on a malformed request.

index.js
1const functions = require('firebase-functions');
2const axios = require('axios');
3const jwt = require('jsonwebtoken');
4
5let cachedToken = null;
6let tokenExpiry = 0;
7
8async function getZoomInfoToken(cfg) {
9 if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
10 // Step 1: generate JWT from credentials
11 // Verify exact payload claims with your ZoomInfo account rep
12 const payload = {
13 iss: cfg.client_id,
14 sub: cfg.username
15 };
16 const signedJwt = jwt.sign(payload, cfg.private_key, {
17 algorithm: 'RS256',
18 expiresIn: '60s'
19 });
20 // Step 2: exchange JWT for Bearer access token
21 // Verify the exact token endpoint URL for your ZoomInfo plan
22 const res = await axios.post(
23 'https://api.zoominfo.com/authenticate',
24 { jwt: signedJwt },
25 { headers: { 'Content-Type': 'application/json' } }
26 );
27 cachedToken = res.data.jwt; // verify response field name with your plan
28 tokenExpiry = Date.now() + 55 * 60 * 1000; // cache ~55 minutes
29 return cachedToken;
30}
31
32exports.enrichLead = functions.https.onRequest(async (req, res) => {
33 res.setHeader('Access-Control-Allow-Origin', '*');
34 if (req.method === 'OPTIONS') return res.status(204).send('');
35 const cfg = functions.config().zoominfo;
36 const { domain, email } = req.body;
37 if (!domain && !email) {
38 return res.status(400).json({ error: 'Provide domain or email' });
39 }
40 try {
41 const token = await getZoomInfoToken(cfg);
42 // Verify endpoint path and request body structure for your plan
43 const body = domain
44 ? { companyMatchKeys: [{ websiteURL: domain }], outputFields: ['id', 'name', 'industry', 'employeeCount', 'revenueRange'] }
45 : { personMatchKeys: [{ emailAddress: email }], outputFields: ['firstName', 'lastName', 'jobTitle', 'companyName', 'linkedInURL'] };
46 const endpoint = domain
47 ? 'https://api.zoominfo.com/enrich/company'
48 : 'https://api.zoominfo.com/enrich/contact';
49 const result = await axios.post(endpoint, body, {
50 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }
51 });
52 res.json(result.data);
53 } catch (err) {
54 const status = err.response ? err.response.status : 500;
55 res.status(status).json({ error: err.message });
56 }
57});

Pro tip: The exact token endpoint URL, JWT payload claims, signing algorithm, and Enrich API request/response schema must be verified with your ZoomInfo account representative — they may differ from the example above depending on your contract tier.

Expected result: The Firebase Cloud Function deploys successfully. Testing it via Postman with a POST body of { "domain": "example.com" } returns a JSON object with company enrichment data. The function returns an error for an empty or malformed input.

3

Step 3: Create a ZoomInfo API Group and API Call in FlutterFlow

With the Cloud Function deployed and tested, configure FlutterFlow to call it. In FlutterFlow, click API Calls in the left navigation panel. Click + Add and choose Create API Group. Name it ZoomInfo and set the Base URL to your Firebase Cloud Function's HTTPS trigger URL (e.g. https://us-central1-your-project.cloudfunctions.net). Inside the ZoomInfo group, click Add API Call and name it EnrichLead. Set the Method to POST and the Endpoint to /enrichLead (or your function's route). You do not need to add an Authorization header — your Cloud Function handles all ZoomInfo authentication internally. Under the Variables tab, add two variables: domain (String, optional) and email (String, optional). In the Body tab, select JSON and set: ```json { "domain": "{{ domain }}", "email": "{{ email }}" } ``` Under the Response & Test tab, paste a sample response JSON from your Cloud Function test — for a company enrichment, this might include fields like name, industry, employeeCount, and revenueRange. Click Generate JSON Paths to create bindings like $.data[0].name, $.data[0].industry, and $.data[0].employeeCount. Verify the exact JSON path structure by running a real call through the Test tab (using a test domain like 'example.com') and examining the actual response shape from your Cloud Function. The structure will match your ZoomInfo plan's response format. Save the API Call.

api_call_config.json
1{
2 "method": "POST",
3 "endpoint": "/enrichLead",
4 "headers": { "Content-Type": "application/json" },
5 "body": {
6 "domain": "{{ domain }}",
7 "email": "{{ email }}"
8 }
9}

Pro tip: Leave the domain variable empty when enriching by email, and leave the email variable empty when enriching by domain. The Cloud Function checks which one is provided.

Expected result: A ZoomInfo API Group containing an EnrichLead API Call appears in the API Calls panel. The Response & Test tab shows JSON Path bindings populated from a real test call against your Cloud Function.

4

Step 4: Wire the Enrich action to a form button with debouncing

Now connect the EnrichLead API Call to your lead capture form in FlutterFlow. On your lead form screen, add a TextField for the company domain (or email) and an 'Enrich' Button. The critical design decision here is to wire the enrichment call to the button tap — not to the TextField's onChange event. Triggering enrichment on every keystroke would call the Cloud Function dozens of times as the user types, burning through ZoomInfo's per-record credit cap extremely quickly. The Enrich button is the correct trigger. Create a page variable called isEnriching (Boolean, initial value false) and another called enrichError (String, initial value ''). In the Action Flow Editor on the Enrich button: 1. First action: Update Page State → set isEnriching to true, clear enrichError. 2. Second action: Backend/API Calls → ZoomInfo → EnrichLead. Map the domain variable to the TextField's value. 3. On success branch: extract the JSON Path values from the action output and use Update Page State to set individual page variables (companyName, industry, headcount, revenueRange). Then set isEnriching to false. 4. On failure branch: set enrichError to a human-readable message and set isEnriching to false. Disable the Enrich button when isEnriching is true (via a Conditional Widget property on the button's disabled state). Show a CircularProgressIndicator when isEnriching is true. Show the enrichError text in a red Text widget, conditionally visible when enrichError is not empty. Bind the auto-filled output fields (Company Name, Industry, Headcount) to their respective page variables. After enrichment succeeds, these fields populate automatically without user input.

Pro tip: Add a 'Clear' button next to each auto-filled field so users can correct enrichment data that is wrong or outdated. ZoomInfo's data is generally high quality but not infallible — especially for small or recently-renamed companies.

Expected result: The Enrich button triggers a single Cloud Function call with the entered domain. After the call returns, the Company Name, Industry, Headcount, and Revenue Range fields populate automatically. The button shows a loading state during the call and an error message on failure.

5

Step 5: Handle errors gracefully and protect the per-record credit cap

ZoomInfo's per-record billing model makes error handling and call discipline more important here than in most integrations. A few implementation details that protect your credit cap and improve user experience: Validate input before calling: In the Action Flow Editor, add a Conditional action before the EnrichLead call. Check that the domain TextField is not empty and contains at least one period (basic domain format check). If validation fails, show a validation message — no credit is consumed. Similarly, validate email format before email-based enrichment. Handle no-match gracefully: ZoomInfo returns a 200 response with an empty data array when no match is found for the input. Parse the array length from the JSON Path response. If the array is empty, show a 'No ZoomInfo data found for this domain' message rather than leaving all fields blank with no explanation. Handle credit exhaustion: ZoomInfo returns an error when the account's credit cap is reached (verify the exact HTTP status and error code with your account rep — it may be 402 or a specific error message in the response body). Catch this in the failure branch of the Action Flow Editor and show a meaningful message like 'Enrichment is temporarily unavailable — contact your admin.' For production apps, consider logging each enrichment call (domain queried, timestamp, whether a match was found) to Firestore so you can audit credit usage and identify if any screen is triggering unexpected enrichment calls. If you need help setting up this credit-tracking pattern alongside the FlutterFlow UI, RapidDev's team builds integrations like this every week — free scoping call at rapidevelopers.com/contact.

Pro tip: Test with a known-good domain (like a Fortune 500 company) and a known-missing domain (like a personal blog URL) to verify your no-match handling and error branches before shipping.

Expected result: The form correctly rejects empty or malformed domain input without calling the API. The no-match state shows a helpful message. Credit exhaustion and other API errors are caught and displayed to the user rather than silently failing.

Common use cases

Sales CRM app with auto-fill company data on lead forms

A mobile CRM app for sales reps includes a 'Add Lead' screen with a company domain field. After the rep types the domain and taps 'Enrich', the app calls ZoomInfo through the Cloud Function and auto-fills company name, industry, headcount, and the contact's title and LinkedIn URL. This eliminates manual data entry and improves lead record quality.

FlutterFlow Prompt

On the Add Lead screen, add a 'Company Domain' text field and an 'Enrich' button. When tapped, call the ZoomInfo enrichment backend and auto-fill the Company Name, Industry, Headcount, and Primary Contact Title fields with the returned data.

Copy this prompt to try it in FlutterFlow

B2B event app that enriches attendee registrations

A B2B events app collects attendee email addresses during in-person events. After submission, the app silently calls ZoomInfo to append job title, company, and seniority level to the registration record stored in Firestore. Sales teams can then see enriched attendee data in the organizer dashboard.

FlutterFlow Prompt

After a user submits their email on the event registration screen, silently enrich their contact record using ZoomInfo and save the returned company and title fields to their Firestore document without any visible loading UI.

Copy this prompt to try it in FlutterFlow

Account-based marketing dashboard enriching target accounts

An ABM dashboard app lets marketers enter a list of target company domains. The app enriches each domain through ZoomInfo and displays a card per company with industry, revenue range, headcount, and key contacts. The enrichment is batched on a scheduled Firebase Cloud Function to avoid burning credits interactively.

FlutterFlow Prompt

Build an Account Dashboard screen that shows a card for each of our target company domains. Each card should display the company name, industry, headcount range, and estimated revenue populated from ZoomInfo enrichment.

Copy this prompt to try it in FlutterFlow

Troubleshooting

Cloud Function returns 401 Unauthorized from ZoomInfo Enrich API

Cause: The cached Bearer token has expired, or the JWT was generated with incorrect claims (wrong algorithm, wrong payload structure, or expired signing time).

Solution: Clear the cached token in the Cloud Function and regenerate the JWT. Verify the JWT payload structure (claims, algorithm, and expiry) matches what ZoomInfo expects for your plan — consult your ZoomInfo account representative if the 401 persists after a fresh token. Check that the private key stored in Firebase environment config is the full PEM-formatted key with correct newline handling.

typescript
1// Force token refresh:
2cachedToken = null;
3tokenExpiry = 0;
4const freshToken = await getZoomInfoToken(cfg);

XMLHttpRequest error in FlutterFlow web build even though the API Call Test tab succeeds

Cause: The Cloud Function is missing CORS headers. Browser-based FlutterFlow web builds enforce CORS; the API Call Test tab runs from FlutterFlow's servers (not the browser) and bypasses this.

Solution: Add CORS headers to your Firebase Cloud Function. Add res.setHeader('Access-Control-Allow-Origin', '*') at the top of the request handler and return 204 for OPTIONS preflight requests. Alternatively, wrap your function with the cors npm package.

typescript
1res.setHeader('Access-Control-Allow-Origin', '*');
2if (req.method === 'OPTIONS') return res.status(204).send('');

ZoomInfo returns a 200 response but the enriched data fields are empty in FlutterFlow

Cause: The JSON Path bindings in the FlutterFlow API Call do not match the actual response structure returned by your Cloud Function, or the ZoomInfo response contains an empty data array (no match for the domain/email).

Solution: Re-open the Response & Test tab for the EnrichLead call, paste the actual JSON response from a successful Cloud Function call, and click Generate JSON Paths to regenerate the bindings. Also add a conditional check for an empty data array and show a 'No match found' state in the UI.

Credits are being consumed faster than expected

Cause: Enrichment is being triggered more frequently than intended — for example, if a Backend Query is set to auto-refresh on page open, or if the action is wired to a TextField onChange event instead of a submit button.

Solution: Audit every place the EnrichLead API Call is triggered. Ensure the call is only fired on explicit button taps or form submissions, never on page load or text input changes. Add logging to the Cloud Function (write a Firestore document per call with timestamp and input) to identify the source of unexpected calls.

Best practices

  • Never put the ZoomInfo private key, client ID, or Bearer token in any FlutterFlow field — the Cloud Function proxy is the only safe location for these credentials
  • Trigger enrichment only on explicit button tap or form submission, never on TextField onChange — ZoomInfo bills per record and keystroke-level calls will drain your credit cap rapidly
  • Validate domain or email format in the FlutterFlow Action Flow Editor before calling the Cloud Function to avoid burning credits on obviously invalid inputs
  • Cache the ZoomInfo Bearer token in the Cloud Function with a buffer before expiry (subtract a few minutes from the token's TTL) to avoid regenerating the JWT on every enrichment call
  • Log each enrichment call to Firestore with the timestamp, domain queried, and whether a match was found — this audit trail helps you track credit usage and identify unexpected API calls
  • Handle the no-match response (empty data array) explicitly in the UI rather than leaving auto-fill fields blank with no explanation
  • Store enriched data to Firestore after a successful call so users do not need to re-enrich the same domain on future app opens
  • Verify all API details — authentication flow, endpoint paths, and response schema — with your ZoomInfo account representative, as they can differ between contract tiers

Alternatives

Frequently asked questions

Can I get ZoomInfo API access without an annual enterprise contract?

No. As of current information, ZoomInfo does not offer a self-serve API tier — API access is bundled with annual enterprise contracts that are commonly quoted at $15,000 or more per year and are gated behind a sales conversation. If you need B2B company data with a self-serve API and a free tier, consider alternatives like Apollo.io or HubSpot's Companies API.

Why can't I generate the ZoomInfo JWT directly in a FlutterFlow Custom Action?

Generating a JWT requires the ZoomInfo private key, which is a cryptographic secret. FlutterFlow Custom Actions compile to Dart code and ship inside the app binary on iOS, Android, and web. Any secret embedded in the binary can be extracted using standard tools. The private key must stay on the server — in a Firebase Cloud Function — where only your code can access it. The function generates the JWT and calls ZoomInfo, returning only the non-sensitive enrichment results to FlutterFlow.

How many API calls does my ZoomInfo contract include?

ZoomInfo billing is per-record enriched rather than per API call, and the exact credit allocation depends on your specific contract terms. Verify your credit cap, what counts as a consumed credit (successful match only vs. all calls), and the cost of exceeding the cap with your ZoomInfo account representative before deploying the integration to production.

What happens if ZoomInfo does not have data for a domain I search?

ZoomInfo returns a 200 HTTP status with an empty or minimal data array when it cannot match the input domain or email to a known company or contact. Your Cloud Function should pass this empty response back to FlutterFlow, and your UI should show a 'No data found for this domain' message rather than leaving form fields blank. Whether an unmatched call still consumes a credit depends on your ZoomInfo contract terms — verify this with your account rep.

Can I use ZoomInfo for consumer contact lookups, not just B2B company data?

ZoomInfo's dataset is focused on B2B contacts and companies — job titles, company firmographics, direct-dial business phone numbers, and professional email addresses. It is not designed or licensed for consumer identity lookups. Using ZoomInfo for consumer data enrichment may violate your contract terms and applicable privacy regulations (GDPR, CCPA). Consult your ZoomInfo contract and legal counsel before using the API for consumer-facing data.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.