Delivery behavior

The payload envelope, the 5-second budget, why there are no retries, and what every delivery status means.

Webhook delivery is deliberately simple, and simple in a way that has consequences for your receiver: one HTTP attempt, five seconds, no retry. Read this page before you decide how much work your handler does inline.

The envelope

Every event except listing.submission arrives in the same top-level shape, with the event-specific fields nested under data.

{
  "event": "connection.location_connected",
  "timestamp": "2026-08-25T14:32:10Z",
  "account_id": 11073,
  "location_id": "279381",
  "data": {
    "platform": "google",
    "connected_account_id": "ba03bc4b-9f8c-4d3b-8e93-9628034c63cc"
  }
}
FieldTypeNotes
eventstringThe dotted event name. Branch on this.
timestampstringISO-8601 UTC, stamped when the delivery was built — not when the underlying change happened.
account_idintegerThe account the event belongs to.
location_idstringA numeric string"279381", not 279381. Not coerced on interaction.*; see the exception below.
dataobjectEvent-specific fields. Documented per event in the event reference.
agency_account_idintegerOnly present when the account is managed by a parent agency account.

Optional fields inside data are omitted when empty rather than sent as null. Code defensively: treat a missing key and an explicit null the same way, and never assume a key that is documented as optional will be present.

Two exceptions

The 5-second budget

Each delivery is a single POST with a 5-second connect timeout and a 5-second read timeout. If your endpoint has not responded in time, the attempt is abandoned and recorded as timeout.

That budget covers your entire handler. The only safe design is:

  1. Verify the signature.
  2. Write the payload somewhere durable — a queue, a table, a log.
  3. Return 200.

Everything else — enrichment, fan-out, calling back into the REST API, anything that touches a third party — belongs on the other side of that queue.

There are no retries

A delivery is attempted exactly once. There is no backoff, no retry queue, and no dead-letter queue. If your endpoint is down for two minutes, the events that fired in those two minutes are gone — they will not arrive later.

Two consequences worth being explicit about:

  • Redirects are not followed. Redirects are disabled outright, so a 301 or 302 from your endpoint is recorded as a failed delivery, not followed to a second URL. Configure your webhooks URL as the final URL — watch for the bare-domain-to-www and the trailing-slash redirects that many web servers add by default.
  • Only a 2xx counts as delivered. 3xx, 4xx and 5xx are all recorded as failures.

Duplicates and ordering

  • You may receive the same event more than once. De-duplicate on the stable identifiers inside data (an interaction id, a post id, a connected-account id) combined with event. Handlers should be idempotent.
  • Ordering is not guaranteed. Events are produced by several independent rails and delivered as they arrive. Do not infer a sequence from arrival order; use the timestamp and the IDs in data, and be prepared for a local_post.published to land before the local_post.created you expected first.
  • profile.updated is debounced to one delivery per location per 60 seconds. This is a real collapse, not a delay: edits inside the window are never delivered, and changed_fields describes only the edit that opened the window. If you need the full current state, read the location back from the REST API when the event arrives. Nothing else is debounced.

Delivery outcomes

Every attempt — including one that never leaves the building — is recorded with a status. These are the exact strings.

The four outcomes and the status strings that record them. 'Blocked' and 'rate limited' mean no HTTP request was made at all, which is why they are worth keeping separate from a genuine failure.
StatusOutcomeWhat it meansWhat to do
successDeliveredYour endpoint returned a 2xx.Nothing.
an HTTP code as a string — e.g. "404", "500"FailedYour endpoint answered with that non-2xx code. 3xx lands here too, because redirects are not followed.Check the path, the redirect chain, and your handler's error rate.
999FailedA response came back with no usable status code.Usually a proxy or load balancer in front of your endpoint.
timeoutFailedNo response within the 5-second budget.Move work off the request path and acknowledge sooner.
network_errorFailedThe host could not be resolved or reached.Check DNS and that the host is publicly resolvable.
errorFailedThe attempt raised something that isn't one of the above — a TLS handshake failure, for instance.Check your certificate chain and server logs.
blocked_urlBlockedThe URL failed its safety check at dial time: not HTTPS, no host, or it resolved to a private, loopback, link-local or metadata address.Point the URL at a public HTTPS host. Note this is re-checked on every delivery, so a DNS change can start blocking a URL that used to work.
endpoint_unverifiedBlockedThe account has a signing secret, but the endpoint has not passed verification — or verification was reset.Re-run Verify endpoint from the dashboard.
rate_limitedRate limitedThe per-minute or per-day cap was exceeded. The event was dropped.Reduce volume or move to a plan with a higher cap. See plans and limits.

A receiver checklist

  • Verify the signature on every request; return 401 and process nothing on a mismatch.
  • Hash the raw body bytes, never a re-serialized object.
  • Return 2xx in well under 5 seconds; queue the work.
  • Make handlers idempotent and key them on the IDs inside data.
  • Don't infer ordering from arrival order.
  • Normalize location_id to a string on the way in.
  • Ignore event values you don't recognize, and still return 200.
  • Serve the webhooks URL as a final URL — no redirects.
  • Reconcile periodically against the REST API; there is no retry to save you.