Two secrets, two directions
Webhooks involve two different credentials. They are not interchangeable.
Each endpoint gets its own signing secret. Rotating one endpoint’s secret never affects any
other endpoint, and a leaked signing secret grants no access to your workspace.
Create an endpoint
Manage endpoints from Settings → Webhooks, or over the API with your API key:Response
https://user:pass@…), and must
not resolve to a private or loopback address.
The payload
Every delivery is aPOST with a JSON body in one envelope shape: metadata plus a
type and its data.
type tells you what happened; data is that event’s payload. Switch on type and you can
add new subscriptions later without changing how you parse the envelope.
Events
chat.assigned covers real customer conversations only — internal workspace chats and the
assistant playground never produce it.The payload is the full chat object. Assignment lives under state: state.assigned_user is
who it landed on (absent means it was unassigned), alongside state.assigned_at,
state.assistant, state.integration_id and state.ai_enabled.broadcast.bounced, broadcast.complained and broadcast.suppressed are per recipient,
not per broadcast — one delivery per affected address, carrying email, message_id, the
provider reason, and contact_id when the address still maps to a contact.GET /orgs/c/webhooks/events.
Verifying a request
Always verify the signature before trusting a payload. Anyone can POST JSON at your URL; the signature is what proves Invent sent it. Every delivery carries these headers:
The signature is an HMAC-SHA256, keyed with your endpoint’s signing secret, over the
raw request body joined to the id and timestamp:
- Sign the raw body bytes, exactly as received. Parsing the JSON and re-serializing it changes the bytes and the signature will not match.
- Compare in constant time (
crypto.timingSafeEqual,hmac.compare_digest), and reject a timestamp older than a few minutes so a captured request cannot be replayed later.
Responding
Return any2xx as soon as you have durably accepted the event — do the real work
afterwards, out of the request. Requests time out after 10 seconds.
Anything that is not a 2xx, plus timeouts and connection errors, counts as a failure and
is retried.
Retries
Failed deliveries are retried with a widening backoff: 5s → 5m → 30m → 2h → 5h → 10h (7 attempts total). After the last attempt the delivery is markedFAILED.
Delivery is at-least-once: the same event can arrive twice if your 2xx was lost on the
way back. Deduplicate on id (or the x-webhook-id header) and make your handler idempotent.
Ordering is not guaranteed — a retry can land after a newer event. Order by created_at
if sequence matters.
An endpoint that fails 30 deliveries in a row is disabled automatically, and the reason is
shown on the endpoint. Re-enabling it (PATCH enabled: true) clears the failure streak.
Deliveries also pause while a workspace has no balance, and resume once it is topped up.