Create a webhook
In AO Cloud, open Integrations, choose AO Webhook, and create it. AO shows three values:
You can reveal or rotate the secret later from the installed integrations
list. Rotating takes effect immediately: requests signed with the previous
secret are rejected, so update your sender at the same time.
Sign each request
Every request carries a timestamp and a signature. The secret itself is never sent.
To compute the signature:
- Build the string to sign: the
X-AO-Timestampvalue, a literal., then the exact bytes of the request body. - Compute HMAC-SHA256 over that string, keyed with the full signing
secret as-is, including its
whsec_prefix. Do not decode or trim it. - Hex-encode the digest in lowercase and prefix it with
sha256=.
Check your implementation
Your signing code should reproduce this signature exactly:Examples
Each example sends one event. SetAO_WEBHOOK_URL to your endpoint URL
and AO_WEBHOOK_SECRET to your signing secret. The Node.js example is an
ES module: save it as a .mjs file.
The request body
The body is anao.webhook.v1 envelope holding 1 to 500 records:
schema must be exactly ao.webhook.v1. The only record type accepted
today is event:
Unknown fields in a record are rejected rather than ignored, so a typo
surfaces as an
invalid_schema error instead of silently dropped data.
Updates and duplicates
Events are identified bysource_event_id within a webhook. Sending a
record with an ID the webhook has already sent updates that event instead
of creating a new one, which makes retries safe. AO keeps the version with
the latest observed_at, so when you update an event — for example to add
closed_at — set observed_at to the time of the change.
Two different webhooks can use the same source_event_id without
colliding.
Responses
A successful request returns200 with the number of records ingested:
Retry
internal_error and network failures. Retries are safe because
records are deduplicated by source_event_id. Other errors will not
succeed unchanged. Re-sign each retry with a fresh timestamp.