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

# Webhook security

> Verify webhook authenticity using HMAC signatures and follow endpoint security best practices.

## Signature verification

Tesouro can sign webhook deliveries with HMAC-SHA512 so you can verify a request came from Tesouro and wasn't tampered with. Signing is **opt-in per subscription** - unsigned by default. Once enabled, every delivery to that subscription includes the three headers below.

To enable signing for your subscription, contact your Tesouro implementation team.

### Headers

| Header                | Example value                   | Description                                                                                                                                                                                                                                                           |
| :-------------------- | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-tesouro-signature` | `t=1746673883,v1=A1B2C3...DEF0` | Signature header. `v1=<hex>` is the 128-character uppercase hex HMAC-SHA512 digest. `t=<unix-seconds>` is the send-time Tesouro stamped on the request, included in the signed input - use it for replay-window checks (see [Replay protection](#replay-protection)). |
| `x-tesouro-key-id`    | `prod-key-2026-01`              | Identifier of the signing key Tesouro used. Use it to select the matching shared secret - Tesouro supports overlapping keys during rotation.                                                                                                                          |
| `x-tesouro-algorithm` | `hmac-sha512`                   | Algorithm used. Currently always `hmac-sha512`.                                                                                                                                                                                                                       |

### Computing the signature

```
signed_input = "{t}.{request_body}"
v1           = HEX( HMAC-SHA512( secret, signed_input ) )
```

`{t}` is the unix-seconds value from `x-tesouro-signature`. `{request_body}` is the raw request body bytes, byte-for-byte as received - don't re-serialize or reformat the JSON before hashing. Compare case-insensitively (the digest is uppercase on the wire).

### Verification steps

1. Extract `t=<unix-seconds>` and `v1=<hex>` from the `x-tesouro-signature` header.
2. Look up the shared secret for the key id in `x-tesouro-key-id`.
3. Construct `signed_input = "{t}.{raw_body}"`.
4. Compute `HEX(HMAC-SHA512(secret, signed_input))`.
5. Reject with `401` if the computed digest doesn't match `v1`.

Example using `openssl`:

```bash theme={null}
SECRET="<your shared secret>"
T="<unix-seconds value from x-tesouro-signature>"

EXPECTED=$( { printf '%s.' "$T"; cat request-body.json; } | openssl dgst -sha512 -hmac "$SECRET" -hex | awk '{print toupper($2)}')
echo "$EXPECTED"
```

<Warning>
  If signing is enabled for your subscription, always verify the signature before processing the event.
</Warning>

## Replay protection

After verifying the signature, also check that the request is fresh:

1. **Reject stale requests.** Compare `t=<unix-seconds>` from `x-tesouro-signature` to the current time and reject anything older than a small window (Tesouro recommends 5 minutes).
2. **Deduplicate on `deliveryId`.** Maintain a cache of recently-seen `deliveryId` values from the envelope and reject any request whose `deliveryId` you've already processed.

## Secret rotation

Signing secrets can be rotated without downtime:

* Add the new secret before removing the old one.
* During the rotation period, accept requests signed by either key - use `x-tesouro-key-id` to select the matching secret per request.
* Remove the old secret only after all in-flight webhooks finish.

Contact Tesouro support to rotate your webhook signing secret.

## Endpoint validation

In addition to signature verification:

* Register only HTTPS endpoints; plain HTTP isn't supported.
* Where possible, restrict your webhook handler to known Tesouro source IPs.
* Return `401` for requests that fail validation.

## Additional options

Tesouro also supports mutual TLS (mTLS) - contact the implementation team to enable it.
