Agent Credential Webhook Events, and Why the Timestamp Is Not Where You Will Look For It

What you get

Subscribe an endpoint and a list of events, and the subscribe response returns a signing secret. That secret appears in that one response and never again.

Deliveries arrive as signed requests, retried with exponential backoff when they fail.

The signature, and the part that trips people

Each delivery carries an HMAC over the request body, keyed by your secret, in a signature header with the algorithm named in the value.

The headers carry the event name and a delivery identifier, and no timestamp. Webhook conventions usually put a timestamp in a header and sign it alongside the body, so a receiver who knows the pattern will look at the headers, see no timestamp, and conclude there is nothing to check freshness against.

There is. It is in the body, and the body is what gets signed. Each delivery's JSON carries its own identifier, its event name, and the time it was created.

So replay defence is available and it is entirely your job:

  1. Verify the signature over the raw body, before parsing it, with a constant-time comparison.
  2. Read the creation time from inside the body and reject deliveries older than a bound you choose.
  3. Store the delivery identifier and refuse a repeat, because retries mean at-least-once and a captured delivery stays validly signed.

Nothing in the protocol forces any of the three, which is why they are the first section rather than a footnote.

The outbound request is hardened better than most

Private, loopback, link-local and cloud-metadata address ranges are refused, and redirects are not followed.

And the check runs again at delivery time, with the address pinned. An endpoint that passed validation when you subscribed and whose name later resolves to an internal address is refused at delivery.

That closes the gap between checking and connecting, which is the one most implementations leave open. It deserves saying plainly, because it is the sort of care that never shows up in a feature list.

Every payload is a whitelist

An agent registration carries the agent's identifier, its decentralised identifier and its custody mode. A credential issuance or revocation carries the credential identifier, the agent identifier and the credential type.

That is the whole payload. No claims, no assurance level, no operator identity, nothing about a subject.

The consequence is the point: a new column added to a table does not appear in your webhook next week. Anything that leaves the building is enumerated rather than filtered, which is the only construction that stays safe as a schema grows.

This page covers agent and credential events and does not claim to list every event the product emits.

Delivery semantics, stated so you can build against them

Idempotent on our side. A delivery already marked delivered is not sent again by a repeat job.

At-least-once from your side. Retries exist, so plan for duplicates rather than assuming they cannot happen.

Attempt counts are honest. A failed attempt is recorded as failed rather than being smoothed into a success, which matters the first time you debug a missing event.

What we have not done

No timestamp header, which is a convention gap rather than a security gap, and the fix is either a header or documentation. This page is currently the documentation.

No published receiver example. The three steps above are a specification, not a tested snippet, and this page does not pretend otherwise.

Keep reading

Agent Credential Webhook Events, and Why the Timestamp Is Not Where You Will Look For It · Solidus · Solidus Agents