Founder DB LogoFounder DB80% OFF
FeaturesPricingFAQ
Log in

Docs

  • Overview
  • Contact variables
  • External API integrations

External API integrations

A Call external API step sends the contact to your API, stores what it finds as contact variables, and continues the sequence. Your API can answer right away or hours later.

How it works

  1. A contact reaches the step. We POST a JSON request to your URL.
  2. Your API answers either right away (sync) with the result in the HTTP response, or later (async): it accepts the request, then POSTs the result to the callbackUrl we sent.
  3. We store the returned variables on the contact and continue on the step's Done path, or on Failed if your API reports failure, errors, or (async) doesn't call back in time.

Protocol version: founderdb.integration.v1. It's in every request and callback response.

Example: research a contact, then email

You want to know which platforms each contact already uses before writing to them, and your research service takes a few hours.

sequence
Contact enrolled
  → Set variables        has_figma = unknown, platforms = unknown
  → Call external API    https://research.example.com/founderdb  (async, give up after 24h)
       Done   → Branch on variable   has_figma is yes
                    Yes → Email (template): "Saw you're on Figma — {{var.pitch_line | …}}"
                    No  → Email (AI): "Pitch the tools they'd benefit from"
       Failed → Email (template): generic intro

Setting the variables to unknown first is optional, but it makes the contact show “Unknown” in the inbox while research runs, and it lets a later branch tell “no answer” apart from “no”.

The request we send

POST to the step's URL with content-type: application/json:

request body
{
  "protocol": "founderdb.integration.v1",
  "callId": "8c1f7a0e-2d4b-4a51-9d0a-6f3a1c2b9e77",
  "mode": "async",
  "callbackUrl": "https://founderdb.co/api/integrations/callback/Qm9n…",
  "expiresAt": "2026-09-25T10:00:00.000Z",
  "contact": {
    "id": "3f1c6c5c-ab32-4711-acf4-c92686c05e87",
    "email": "jane@acme.com",
    "name": "Jane Doe",
    "product": { "name": "Acme", "websiteUrl": "https://acme.com" },
    "variables": { "has_figma": null, "platforms": null }
  },
  "sequence": { "id": "…", "name": "Main sequence" },
  "step": { "id": "node_4" },
  "sentAt": "2026-09-24T10:00:00.000Z"
}
HeaderValue
x-founderdb-call-idSame as callId. Use it to deduplicate.
x-founderdb-signaturesha256=<hex>: HMAC-SHA256 of the raw body with the step's signing secret. Only sent when a secret is set.
user-agentFounderDB-Integrations/1
Your headersAny headers added on the step, e.g. Authorization.

expiresAt is null for sync steps. The callbackUrl belongs to this one contact at this one step.

Verifying the signature

node.js
import crypto from "node:crypto";

function isFromFounderDB(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Sign-check the raw bytes you received, before parsing the JSON.

The result

Whether it comes back in the response (sync) or in a callback (async), a result looks like this:

result
{
  "status": "completed",
  "variables": {
    "has_figma": true,
    "platforms": ["figma", "stripe", "notion"],
    "team_size": 12,
    "pitch_line": "Your Figma files could ship straight to Acme."
  },
  "message": "optional, shown on the contact's timeline if it failed"
}
FieldMeaning
statuscompleted (the default) → Done path. failed → Failed path. pending → still working (async only; nothing changes).
variablesKey → value. Values can be text, a number, true/false, a list of strings, or null for unknown. New keys are created automatically. See contact variables.
messageOptional. Up to 1,000 characters.

Sync: answer in the response

  • We wait up to 60 seconds for your response.
  • A 2xx with a result body continues right away. A 2xx with an empty or non-JSON body counts as completed with no variables.
  • A non-2xx status, a timeout or a network error takes the Failed path.
sync response
HTTP/1.1 200 OK
content-type: application/json

{ "variables": { "has_figma": true } }

Async: call back later

  1. Accept the request within 20 seconds with any 2xx, e.g. 202 Accepted. Only a body with an explicit "status": "completed" or "failed" is treated as the final result. Anything else means “working on it”.
  2. The contact waits at this step. Nothing else is sent to them meanwhile.
  3. When you're done, POST the result to callbackUrl. The sequence continues within seconds.
  4. If no result arrives before expiresAt (the step's “give up after”, 24 hours by default), the step takes the Failed path.
callback
curl -X POST "$CALLBACK_URL" \
  -H "content-type: application/json" \
  -d '{"status":"completed","variables":{"has_figma":true,"platforms":["figma","stripe"]}}'

Callback responses

StatusBodyMeaning
200{"ok":true,"alreadyProcessed":false,"status":"completed"}Stored; the sequence continues.
200{"ok":true,"alreadyProcessed":true,"status":"timed_out"}This call was already settled (an earlier callback, or it timed out). Nothing changed.
200{"ok":true,"status":"pending"}You sent status pending. Still waiting.
400{"ok":false,"error":"…"}The body isn't valid JSON or doesn't match the result shape.
404{"ok":false,"error":"Unknown callback"}No such callback URL.

Rules worth knowing

  • Single use. Each callback URL settles its call once. Retrying is safe: repeats get alreadyProcessed: true, so retry on network errors or 5xx until you get a 200.
  • One call per contact per step. If a sequence loops back through the step, that's a new call with a new callId and callbackUrl.
  • The callback URL is the credential. It's long and random. Keep it private. Callbacks aren't signed.
  • Variables are merged. Keys you don't return are left as they are. Return null to mark one unknown.
  • Everything is logged. Variable changes, failures and timeouts show on the contact's timeline; each call shows in the sequence's run log.
Pausing a contact's sequence while it waits is fine. Their results are still stored on the contact. If you resume the sequence, the step runs again and makes a new call; the old callback URL stops working.
Founder DB

Real-time launch intelligence + contact enrichment for founders, builders, and growth teams.

© 2026 Founder DB. All rights reserved.

Product

  • Features
  • Pricing
  • FAQ
  • Docs
  • Sign in

Company

  • Contact
  • Opt Out
  • Privacy
  • Terms