Webhooks
Webhooks
AnyoneIn POSTs a signed confirmation.updated event to your webhookUrl when a confirmation gets an outcome, gives up or is cancelled.
Set webhookUrl when you create a confirmation and AnyoneIn calls it whenever there's something to act on. There's one event type, confirmation.updated, and its data is the whole confirmation, so you rarely need to call back for more.
When it fires
| What happened | data.status |
|---|---|
| The customer confirmed, or named someone else who'll be in | confirmed |
The customer picked one of your rescheduleOptions | rescheduled |
| A clear answer that needs a person: a different day, cancel, wrong person, opt-out, wants a person | action_needed |
| Every step went out and nobody answered | unreachable |
| You cancelled it | cancelled |
It doesn't fire for each email, text or call going out, or while the conversation is still going: you hear about outcomes, not chatter. If a customer writes again after the confirmation has closed and the agent reads a new answer, you get another event with the new status and outcome.
The request
Content-Typeheaderapplication/json.X-AnyoneIn-Signatureheadersha256=followed by the lowercase hex HMAC-SHA256 of the raw request body, keyed with your webhook signing secret.typestringAlways
confirmation.updatedtoday.dataobjectThe full confirmation object, including
events.
Verifying signatures
Always check the signature before you trust a webhook. Compute the HMAC-SHA256 of the raw body bytes with your signing secret, add the sha256= prefix, and compare it with the header using a constant-time comparison.
The two mistakes that break verification:
- Parsing before hashing. Re-serialising the JSON changes the bytes. Hash the body exactly as it arrived.
- Comparing with
==. UsetimingSafeEqual,hmac.compare_digestorhash_equals, so the comparison doesn't leak timing.
Roadmap
The signature covers the body only; there's no timestamp in it yet. A signed timestamp for replay protection, and secret rotation with two valid secrets at once, are on the roadmap. Until then, treat events as idempotent (below) and only accept them over https.
Handling deliveries
- Reply fast. Return any
2xxwithin 8 seconds, then do the work. The request times out after 8 seconds. - Be idempotent. You may see the same confirmation more than once, and a later event can change an earlier outcome. Key your handling on
data.idwithdata.updatedAt, and let the newest win. - Match it back with
data.externalId, your own reference. - Use
data.outcome.summaryas the note for your team. It's one plain sentence written for ops.
Delivery and retries
Each event is sent once. AnyoneIn records the result on the confirmation as webhook: { "status": 200, "at": "…" } for the response it got, or { "error": "…", "at": "…" } if it couldn't connect.
- The URL must be
https, and must resolve to a public address. Private and internal addresses are refused. - Redirects aren't followed. Point
webhookUrlat the final URL. - The request times out after 8 seconds.
Roadmap
Automatic retries with backoff, a delivery log and a "resend" button are on the roadmap. Until they land, reconcile once a day: list recent confirmations and check any that closed without your system hearing about it. webhook.status tells you what we got back.
Acting on the outcome
| Status | Suggested action in your system |
|---|---|
confirmed | Mark the job ready for dispatch. Pass outcome.signerName to the crew |
rescheduled | Move the booking to order.date and order.window. The customer has already been told |
action_needed | Create a task with outcome.summary. Check outcome.intent: opt_out means stop messaging them |
unreachable | Have someone call before dispatch, or hold the job |
cancelled | Nothing |
Base URL https://api.anyonein.co.uk/v1 is a placeholder until the production domain is confirmed. Found a mistake? Tell us.