Endpoint verification and secret rotation

Webhooks

Two additions to webhooks: Layout checks that an endpoint answers before it sends it order events, and a signing secret can now be replaced on your own schedule.

What changed

  • Endpoint verification. Saving a new subscription sends one signed endpoint.verification event. The subscription stays off until your endpoint answers it with a 2xx, then turns on by itself. It carries "test": true and names no order. It is signed with the environment selected in the console first, and again with the other environment’s secret if your endpoint answers that with a 4xx or 5xx. Verify on the subscription sends another.
  • Changing a URL drops nothing. A verified subscription keeps delivering to its current URL until the new one answers its verification event with a 2xx, then switches over.
  • Secret rotation with overlap. Rotate on the Webhooks page replaces the signing secret for the selected environment. For the next 24 hours every delivery carries two signatures, t=<unix>,v1=<new>,v1=<old>. A receiver that accepts any matching v1 verifies with either secret. A second rotation inside that window is refused unless you choose to end the overlap, and rotating a production secret needs a sign-in from the last 15 minutes.
  • The delivery contract is written down in Delivery and retries: at least once, no ordering guarantee, a 3xx counts as a failure, and an attempt fails if your endpoint sends nothing for 5 seconds or has not finished answering within 10. Retry waits are approximate.
  • The verification snippet is corrected. The one published before this compared the signature with the = of v1= still attached, so it could not pass. Use the verifier in Verify the signature.

Why it matters

A typo in a URL now shows up in the console the moment you save it, rather than as a dead-lettered order event later. Rotation lets you replace a secret on your own schedule: deploy the new one any time inside the 24 hours.

What to do

Before you rotate, make sure your verifier accepts the request when any v1 matches. One that reads only the first v1 fails on every delivery from the moment you rotate until it holds the new secret:

const signatures = header.split(",")
  .filter((p) => p.startsWith("v1="))
  .map((p) => p.slice(3));
const ok = signatures.some((s) => safeEqual(s, expected));

The full verifier, with the 5 minute timestamp check your endpoint should enforce, is in Verify the signature. If your handler returns an error for events it does not recognise, return 2xx for endpoint.verification too, or acknowledge anything with "test": true.

Breaking changes

None. Subscriptions that already exist stay on without being verified again, and a delivery carries one signature unless you rotate.

All changes