Webhooks
The platform POSTs to your endpoint when an async operation finishes, so you don’t have to
poll. See the API reference for the exact payload schema of each event —
segment.ready,
segment.failed,
delivery.completed,
delivery.failed — this guide covers setup and
signature verification.
Events
Section titled “Events”| Event | Fires when |
|---|---|
segment.ready |
An async segment build succeeded (file match, similarity, or propensity). |
segment.failed |
An async segment build failed. |
delivery.completed |
A file export/download is ready. |
delivery.failed |
A file export/download failed. |
Where to point events
Section titled “Where to point events”Three ways, checked in this order:
- Per-request
webhook_url— pass it on the individual call (e.g.POST /v1/match/fileorPOST /v1/match/{id}/deliveries). Used for that call only. - Org-configured URL — set once via
PUT /v1/settings/webhook, used for everything that doesn’t specify its ownwebhook_url. - Durable subscriptions —
POST /v1/webhook-subscriptionswith atarget_urland aneventsarray, for when you want a standing endpoint that receives every event of a given type regardless of which call triggered it (this is also the path OAuth-connected apps use to receive events for the account that authorized them).
Omitting webhook_url on a request falls back to the org-configured URL; it does not
also call any durable subscriptions for that same event — those are independent delivery
paths, not a fallback chain.
Setting up a durable subscription
Section titled “Setting up a durable subscription”curl -X POST https://api.infiniteaudience.ai/v1/webhook-subscriptions \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "target_url": "https://your-app.example.com/webhooks/infinite-audience", "events": ["segment.ready", "segment.failed", "delivery.completed", "delivery.failed"] }'{ "id": "whsub_a1b2c3", "target_url": "https://your-app.example.com/webhooks/infinite-audience", "events": ["segment.ready", "segment.failed", "delivery.completed", "delivery.failed"], "secret": "whsec_9f8e7d6c5b4a...", "status": "active"}Requires the account scope — or, for OAuth-connected apps, you can manage a subscription
tied to your own grant without it.
Verifying signatures
Section titled “Verifying signatures”If a signing secret is configured (from Settings, or from a durable subscription’s secret),
every outbound dispatch includes:
X-CF-Signature: sha256=<hmac-hex>computed as HMAC-SHA256(secret, raw-request-body) — over the raw bytes of the request
body, before any JSON parsing. Verify it like this:
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhookSignature(rawBody: Buffer, signatureHeader: string, secret: string): boolean { const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex'); const actual = Buffer.from(signatureHeader); const expectedBuf = Buffer.from(expected); if (actual.length !== expectedBuf.length) return false; return timingSafeEqual(actual, expectedBuf);}import hashlibimport hmac
def verify_webhook_signature(raw_body: bytes, signature_header: str, secret: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(signature_header, expected)Retries
Section titled “Retries”Every delivery path — per-request webhook_url, the org-configured URL, and durable
subscriptions — goes through the same retrying dispatcher, up to 10 attempts total per
event. 5xx, 408, 429, and connection timeouts are retried; 410 Gone stops retrying
immediately (it’s the documented signal for “this endpoint was intentionally removed,” not a
transient failure). A durable subscription that racks up 20 consecutive failed deliveries
is automatically disabled — check its status if events seem to have silently stopped.
Respond 2xx quickly and do slow processing (writing to a queue, calling other services)
asynchronously after — a slow handler risks looking like a failure and triggering a retry for
an event you actually already received.