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

# Verify signatures

> Authenticate requests using the signing secret and original request bytes.

Finosu sends an HTTP `POST` containing the [event envelope](https://docs.finosu.com/api-reference/webhooks/overview.md).
Read the original request body as bytes before JSON middleware changes it.

## Get your signing secret

Finosu provides the endpoint's signing secret when it is created or rotated.
Store it in a secret manager or server environment variable. It is not your API
key and must not appear in browser code or logs.

## Request headers

| Header | Value |
| - | - |
| `Content-Type` | `application/json` |
| `X-Finosu-Signature` | `t=<unix-seconds>,v1=<hex-hmac-sha256>` |
| `X-Finosu-Event-Id` | Same event UUID as the JSON body's `id`. |
| `Idempotency-Key` | Opaque delivery identifier, stable across retries for this endpoint. |
| `Authorization` | `Bearer <configured-token>`, only when configured. |

The signature covers the timestamp and raw body, not the other headers. Verify
company and environment using the signed body and use its `id` for event-level
deduplication. If using a header identifier, confirm it matches the body or store
it alongside the verified event identity. A bearer token does not replace
signature verification.

## Verify the signature

1. Extract `t` and `v1` from `X-Finosu-Signature`.
2. Reject malformed headers and timestamps outside your tolerance. Five minutes
   is a suggested receiver policy, not a sender-enforced setting. Check both old
   and future timestamps and keep your server clock synchronized.
3. Compute HMAC-SHA256 using the signing secret's UTF-8 bytes as the key. The signed
   message is the ASCII timestamp, a period, and the original request-body bytes.
   Do not base64-decode the secret or parse/re-serialize the JSON before verifying.
4. Compare the hexadecimal digest in constant time. Only then parse and process
   the JSON.

Save the following standard-library verifier as `verification.py`. The
[quickstart](https://docs.finosu.com/api-reference/webhooks/quickstart.md) imports this function in a working
test receiver. Business events also need [durable acceptance](https://docs.finosu.com/api-reference/webhooks/receiving.md).

```python theme={null}
import hashlib
import hmac
import re
import time


def verify_finosu_webhook(
    raw_body: bytes,
    signature_header: str,
    secret: str,
    *,
    now: int | None = None,
    tolerance_seconds: int = 300,
) -> bool:
    if not secret or tolerance_seconds < 0 or len(signature_header) > 512:
        return False
    match = re.fullmatch(r"t=([0-9]{1,12}),v1=([0-9a-f]{64})", signature_header)
    if match is None:
        return False
    timestamp_text, supplied_digest = match.groups()
    timestamp = int(timestamp_text)
    current_time = int(time.time()) if now is None else now
    if abs(current_time - timestamp) > tolerance_seconds:
        return False
    signed_bytes = timestamp_text.encode("ascii") + b"." + raw_body
    expected_digest = hmac.new(
        secret.encode("utf-8"), signed_bytes, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_digest, supplied_digest)
```

This example accepts the currently emitted signature format only. It does not
claim support for future signature schemes or multiple simultaneous secrets.
Never log the secret or authorization token. Apply a request-body size limit
appropriate to your integration; this contract does
not define a universal maximum event size.

## Troubleshooting

| Symptom | Check |
| - | - |
| Signature mismatch | Read the raw bytes before JSON parsing; use the secret for this endpoint without base64 decoding it. |
| Missing signature | Confirm your proxy forwards `X-Finosu-Signature`. Header names are case-insensitive. |
| Expired timestamp | Synchronize the receiver clock and check the header's `t`, not the event's `occurred_at`. |
| Verification fails after rotation | Update the receiver with the replacement secret, then request a new test before reactivation. |

See [delivery and retries](https://docs.finosu.com/api-reference/webhooks/receiving.md) for acknowledgment,
deduplication, and recovery behavior.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.