Signing

Every delivery is signed with Standard Webhooks: the headers, the HMAC scheme, secret rotation with an overlap window, and how your customers verify.

Every delivery Repost makes is signed. There is no unsigned mode, and a delivery with no usable secret is refused rather than sent bare. The scheme is Standard Webhooks, so your customers can verify with existing libraries in any language instead of reviewing custom crypto. The specification leaves several things optional; Repost enforces them rather than making them a choice.

What arrives at an endpoint

POST /webhooks HTTP/1.1
content-type: application/json
user-agent: Repost-Webhooks/1
webhook-id: msg_01JZX3NV7Q9WD9F2M6K4T8RSAB
webhook-timestamp: 1784289600
webhook-signature: v1,K5oZfzN95Z9UVu1EsPQqUIoRQOU...
 
{"type":"order.created","timestamp":"2026-07-17T12:00:00.000Z","data":{...}}
HeaderContents
webhook-idThe message id. Stable across retries, replays, and redeliveries, which makes it the dedupe key for consumers.
webhook-timestampUnix seconds at attempt time. Verifiers reject stale timestamps to prevent replay attacks.
webhook-signatureOne or more space-separated signatures, each in the form v1,<base64>.

The signature is HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{body}, keyed with the endpoint's secret. Custom endpoint headers are included in the request as configured, but the webhook-*, content-type, and user-agent headers cannot be overridden.

Secrets

Each endpoint has its own signing secret in the Standard Webhooks format, whsec_ followed by a base64 key. It is returned once when the endpoint is created. After that, it can be read over the API or revealed by the customer in the portal's Secret tab:

curl https://api.repost.sh/v1/endpoints/{id}/secret \
  -H "Authorization: Bearer $REPOST_TOKEN"

The response lists every signing-eligible secret version, newest first. Secrets are encrypted at rest.

Rotation

curl -X POST https://api.repost.sh/v1/endpoints/{id}/secret/rotate \
  -H "Authorization: Bearer $REPOST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"expiresInSeconds": 172800}'

Rotating creates a new secret version and keeps the previous one valid through an overlap window: 24 hours by default, configurable up to 30 days. During the overlap, every delivery carries one signature per live secret in the webhook-signature header, and Standard Webhooks verifiers accept a message when any of them matches. The consumer can switch to the new secret at their own pace inside the window without anything failing to verify.

Customers can rotate their own secrets in the portal. Read-only portal links hide secrets entirely.

What to tell your consumers

Verification is three checks, and Standard Webhooks libraries do all of them given the raw body, the three headers, and the whsec_ secret:

  1. Recompute HMAC-SHA256(secret, "{webhook-id}.{webhook-timestamp}.{raw body}") and compare it against each offered signature, in constant time.
  2. Reject stale webhook-timestamp values. Libraries default to a few minutes of tolerance.
  3. Dedupe on webhook-id, since delivery is at-least-once.

Two mistakes come up often enough to warn about: verifying a re-serialized body instead of the raw bytes, and comparing against a single signature instead of iterating the space-separated list, which breaks on the first rotation. The event docs and the portal's catalog give consumers sample payloads to test against.

Continue