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

# Integrate webhooks with signature checks

> A copy-paste prompt that builds a Lumx webhook endpoint correctly — raw body, HMAC over the signed content, secret rotation, replay and retries.

Paste this prompt into your coding agent to build a Lumx webhook endpoint that verifies signatures and survives retries. The agent builds an endpoint that verifies each signature over the raw body, survives secret rotation, deduplicates retries, and confirms state with the API before acting.

## Before you start

* A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io), with its signing secret in your own secret store.

## Prompt

```text Prompt theme={null}
You are a senior backend engineer building the Lumx webhook receiver for an existing service.

Ground truth. Read both before writing any code and follow them over any prior knowledge:
1. https://docs.lumx.io/developer/webhooks — the signature scheme, the event catalog and the retry schedule. Also read https://docs.lumx.io/llms.txt for the rest of the documentation.
2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, for the resource shapes that arrive inside the event payload.

1. Capture the raw request body before any JSON middleware parses it. Signature verification runs over bytes; a re-serialized body fails and the failure looks like a wrong secret.
2. Read the three headers: webhook-id, webhook-timestamp, webhook-signature.
3. Build the signed content as "{webhook-id}.{webhook-timestamp}.{body}", compute HMAC-SHA256 with the signing secret, and base64-encode the result. The secret arrives prefixed with whsec_ — strip the prefix and base64-decode the remainder before using it as the key.
4. Accept any valid signature in the header, not the first one. The header holds a space-delimited list of version-prefixed signatures, and during a secret rotation Lumx signs with the old and the new secret for 24 hours. Compare in constant time.
5. Reject anything older than your replay window using webhook-timestamp, and deduplicate on webhook-id — Lumx delivers up to eight times with increasing backoff, so the same event will arrive twice.
6. Return 2xx immediately, then process asynchronously. Route by eventType over the documented families: onramp.*, offramp.*, transfer.*, customer.*, customer.limit_request.*, account.* and destinations.*.
7. Treat the payload as a notification, not as state. Before you credit, release or settle anything, confirm with GET /transactions/{id} or GET /customers/{id}.

Constraints:
- Do not trust an event whose signature did not verify, including in sandbox.
- Do not enumerate event types from memory. Take the list from the docs page and flag any type your code receives that is not on it.
- The envelope is eventId, eventType and data. Anything else you need is inside data and its shape follows the resource.
- Do not state a rate limit for the confirmation calls. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff.

Deliverables:
- A verification function with unit tests covering a good signature, a tampered body, an expired timestamp and a rotation header with two signatures.
- An idempotent dispatcher keyed on webhook-id.
- A replay script that posts a stored event to the local endpoint so the handler can be tested without waiting for a transaction.
```

## How to use

1. Register the endpoint in the Dashboard under Developers → Webhooks and copy the signing secret into your own secret store before running the agent.
2. If your infrastructure filters inbound IPs, take the published list from the webhooks page and add it yourself.
3. Run the rotation test after the agent finishes: rotate the secret in the Dashboard and confirm nothing drops during the 24-hour overlap.

## What the prompt builds

| **Endpoint** | **What the agent uses it for** |
| :- | :- |
| `GET /transactions/{id}` | Confirms a transaction's state before acting on an event |
| `GET /customers/{id}` | Confirms a customer's state before acting on an event |

## Errors to expect

These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`.

| **Code** | **Status** | **When it happens** |
| :- | :- | :- |
| `TRANSACTION_NOT_FOUND` | 404 | Transaction does not exist. |
| `CUSTOMER_NOT_FOUND` | 404 | Customer does not exist. |
| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. |

## Related resources

<CardGroup cols={2}>
  <Card title="Webhooks" href="/developer/webhooks">
    Event catalog, signatures, and retries.
  </Card>

  <Card title="Idempotency" href="/developer/idempotency">
    Safe retries with Idempotency-Key.
  </Card>

  <Card title="Integrate on-ramps from fiat to stablecoin" href="/prompts/integrate-onramp">
    Take fiat in and deliver stablecoin to the customer's wallet.
  </Card>

  <Card title="Integrate off-ramps to local bank rails" href="/prompts/integrate-offramp">
    Pay out from a stablecoin balance to a local bank account.
  </Card>
</CardGroup>


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