> ## Documentation Index
> Fetch the complete documentation index at: https://repost.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing and Verification

> 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](https://www.standardwebhooks.com), so your customers can verify with existing libraries in any language instead of reviewing custom crypto.

## What arrives at an endpoint

```http theme={"languages":{"custom":["/languages/repost.json"]}}
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":{...}}
```

| Header              | Contents                                                                                                       |
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
| `webhook-id`        | The message id. Stable across retries, replays, and redeliveries, which makes it the dedupe key for consumers. |
| `webhook-timestamp` | Unix seconds at attempt time. Verifiers reject stale timestamps to prevent replay attacks.                     |
| `webhook-signature` | One 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](/docs/send/endpoints) 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:

```bash theme={"languages":{"custom":["/languages/repost.json"]}}
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

```bash theme={"languages":{"custom":["/languages/repost.json"]}}
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](/docs/send/delivery).

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](/docs/send/event-docs) and the portal's catalog give consumers sample payloads to test against.

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Customer portal" icon="app-window" href="/docs/send/portal" cta="Self-service" arrow="true">
    Where customers reveal and rotate these secrets themselves.
  </Card>

  <Card title="Delivery & retries" icon="shield-check" href="/docs/send/delivery" cta="Reliability" arrow="true">
    The pipeline every signed request travels through.
  </Card>

  <Card title="Customers & endpoints" icon="users" href="/docs/send/endpoints" cta="Manage" arrow="true">
    Endpoint creation, where each secret starts.
  </Card>
</Columns>
