Webhook Protocol v1.0
AON sends the supported conversion event to the HTTPS endpoint configured
for a Developer Application. Verify every delivery before parsing JSON or
applying a business side effect.
Canonical source and runtime evidence are different
The protected Protocol v1.0 postback specification and schema fixtures define the canonical contract. Publication does not prove that a particular runtime, OpenAPI document, SDK, or Portal follower has deployed and validated it. Treat each follower as unvalidated until its owner publishes evidence; production support requires separate runtime and release evidence.
Required headers
Every Protocol v1.0 delivery uses POST, Content-Type: application/json, and
exactly one value for each security/control header:
| Header | Required value or responsibility |
|---|---|
AON-Protocol-Version | Exact value 1.0 |
X-AON-Key | Opaque callback key id selecting one current or grace secret |
X-AON-Timestamp | Canonical unsigned Unix epoch seconds for this attempt |
X-AON-Signature | Lowercase-hex HMAC-SHA256 over the exact wire inputs |
Reject a missing, empty, or duplicate header before HMAC verification or JSON parsing. Never choose the first or last value from duplicate security headers.
Verify the raw body
For every attempt, AON signs this byte string:
POST\n{request-target}\n{exact-raw-body}\n{timestamp}request-target is the exact origin-form path and raw query. Preserve repeated
slashes, percent encoding, query order, and spelling. exact-raw-body is the
byte sequence received on the wire. Verify the HMAC over those bytes before
parsing or re-serializing JSON, compare the lowercase hexadecimal signature in
constant time, then validate the closed conversion payload.
X-AON-Timestamp is the attempt signing time. The payload timestamp is the
conversion event time, normalized to UTC; the two fields are intentionally
different.
Idempotency
Use (agent_id, event_id) as the durable idempotency key and retain the
SHA-256 digest of the exact raw body for at least 24 hours.
- First valid key and body: apply the business effect once and store the digest in the same transaction.
- Same key and same digest: return 2xx without another business effect.
- Same key and different digest: return HTTP 409 and do not process it.
An in-memory cache or SDK signature verifier does not provide this durable transaction.
Five delivery attempts
Any 2xx response completes delivery. AON does not follow redirects and ignores the response body. A timeout, 3xx, or other non-2xx response follows one fixed five-attempt ladder:
| Attempt | Delay after the previous attempt |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
There is no sixth attempt. Across retries, event_id and the exact raw body
stay frozen. Each attempt gets a fresh timestamp and matching signature.
Frozen and current facts
Configuration changes affect different parts of the delivery lifecycle:
- Event-time opt-in is frozen. A conversion records whether it was eligible when the event was accepted. Disabling the current subscription does not cancel an already eligible conversion.
- Target URL is frozen at outbox creation. Changing the endpoint affects future outbox entries, not the target already stored on a queued entry.
- Wire version and raw body are frozen at outbox creation. A v1.0 row stays v1.0 across every attempt and is never downgraded as a fallback.
- Signing secret and key version are current per attempt. The worker reads
the application's current callback secret and key version when it attempts
delivery. A rotation can therefore change
X-AON-Keyand the signature for an already queued entry while its target and body remain unchanged.
Keep the current and previous key generations available during your receiver's rotation window. Unknown or expired key ids must fail closed; do not try every secret as a fallback.
Configuration is not delivery health
A saved endpoint, enabled subscription, configured secret, or successful Portal response is a configuration fact. It is not proof that a worker is running or that a delivery reached your receiver. Runtime support is closed by deployment observation and a signed callback canary, not by this static page.