webhooks:read scope (to read) and webhooks:write scope (to create, update, or delete).
Create an endpoint
Create a webhook endpoint with your HTTPS URL and the events you care about. The response includes the signing
secret — this is the only time it’s returned, so store it.Verify the signature
On each incoming request, verify the
X-Audienceful-Signature header against the raw request body using your secret. See Verifying signatures for the scheme and code examples.Available events
Fetch the current event vocabulary fromGET /webhook-events. Events are grouped by area:
Contacts
| Event | Fires when |
|---|---|
person.created | A new contact is added to your workspace. |
person.updated | A contact’s details change — email, name, custom fields, subscription status, etc. Automatic engagement counters (open/click counts, last activity) are excluded, so ingestion never spams you. |
person.deleted | A contact is deleted from your workspace. |
person.unsubscribed | A contact unsubscribes or is marked unsubscribed (the unsubscribed state goes from unset to set). A single unsubscribe also emits person.updated — subscribe to whichever you want. |
| Event | Fires when |
|---|---|
bulk_email.sent | A bulk email finishes sending. Exactly one summary event per send, with the subject and a summary of the recipients. |
| Event | Fires when |
|---|---|
audience.member_added | A contact enters an audience. |
audience.member_removed | A contact leaves an audience. |
Real-time changes only. Events fire for real-time, per-contact activity — a form signup, an API call, a manual edit, a single delete. High-volume bulk operations are intentionally webhook-silent so a large job can’t emit tens of thousands of deliveries at once:
- CSV imports and bulk upserts don’t fire
person.created,person.updated, oraudience.member_*. - Mass deletes don’t fire
person.deleted. - A full-audience resync (e.g. after an import) doesn’t fire
audience.member_added/audience.member_removed.
person.updated replaces the former person.changed event, and now fires on any meaningful change to a contact (not just audience-relevant fields). Existing endpoints subscribed to person.changed were migrated automatically.Payload format
Every delivery is JSON with anevent name and a data object:
data depends on the event.
person.created · person.updated · person.unsubscribed
person.created · person.updated · person.unsubscribed
These three events share the same contact payload.
extra_data is keyed by each custom field’s data_name.person.deleted
person.deleted
The deleted contact is gone, so the payload is a minimal identifier.
bulk_email.sent
bulk_email.sent
A summary of the completed send.
recipients is the recipient count. draft is null for sends with no associated draft (e.g. SMS).audience.member_added · audience.member_removed
audience.member_added · audience.member_removed
One delivery per contact–audience pair. If a single recompute changes several audiences for a contact, you receive one delivery per audience.
Delivery headers
Every delivery carries these headers:Delivery, retries & auto-disable
- Deliveries are retried on any non-2xx response or network error, with exponential backoff — roughly
30s, 1m, 2m, 4m, 8m, …(each interval jittered ±15% and capped at 4 hours), up to 8 retries over about 2 hours. - Respond
2xxto acknowledge. Respond410 Goneto permanently unsubscribe — the endpoint is disabled withdisabled_reason: "410_gone". - After 20 consecutive failed deliveries (each counted only once its retries are exhausted) an endpoint auto-disables with
disabled_reason: "too_many_failures". Re-enable it withPATCH{ "is_active": true }, which also clears its failure state. - You can inspect recent attempts via the delivery log and send a test event with ping. Delivery log rows are retained for 30 days.
Delivery rate limit
Each endpoint is capped at 10 deliveries per second. When a burst of events exceeds that (say, a few hundred contacts edited in quick succession), the excess deliveries are automatically re-queued and delivered a moment later — they are never dropped. Being throttled does not count as a failed attempt, so a busy endpoint can never trip the auto-disable threshold on rate limiting alone. Sustained throughput averages the cap.Automation “Send Webhook” action
Automations can include a Send Webhook step. When a contact reaches that step, Audienceful POSTs to the configured URL. This is separate from the webhook endpoints described here (it fires from inside an automation, not from an event subscription), but uses the same signing scheme.X-Audienceful-Signature and X-Audienceful-Event (automation.webhook) headers, signed with the action’s own secret (shown in the action config) — verify it the same way. Delivery is fire-and-forget with retries: a slow or failing endpoint never blocks or stalls the contact’s progress through the automation.
