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

# Set up a webhook endpoint

> Request an endpoint, choose the events you want, and prepare your server to receive them

## Request an endpoint

Webhook endpoints are set up by Pazy for now. Email [customer-support@pazy.io](mailto:customer-support@pazy.io) with:

| Detail | Notes |
| - | - |
| Organization | The Pazy organization the events belong to |
| Endpoint URL | Must meet the [URL requirements](#url-requirements) below |
| Event types | The events to subscribe to, from the [event reference](/webhooks/events/overview) |
| Environment | Sandbox, production, or both. Use separate URLs for each |
| Timeout (optional) | How long Pazy waits for your response. Default 10 seconds, maximum 30 |

We reply with your **signing secret**. Store it in a secrets manager or environment variable right away. You need it to [verify every delivery](/webhooks/verify-signatures).

<Warning>
  Treat the signing secret like a password. Don't commit it to source control or expose it in client-side code. Anyone with the secret can forge webhooks that appear to come from Pazy.
</Warning>

You can ask us to change the event list, URL or timeout at any time. A self-serve setup in the Pazy app is planned.

## URL requirements

* **HTTPS only** in production. Plain `http://` URLs are rejected.
* **Publicly reachable.** Pazy won't deliver to `localhost`, private or link-local addresses (such as `10.x.x.x`, `172.16–31.x.x`, `192.168.x.x`, `127.x.x.x`), or to any hostname that resolves to one of them.
* **No redirects.** Pazy doesn't follow `3xx` responses, so a redirect counts as a failed delivery. Register the final URL.
* **Unique per organization.** A URL can be registered once per organization. You can register several different URLs, each with its own secret and event list.

<Info>
  If your servers sit behind a firewall, ask us for our egress IP addresses so you can allowlist them. We share them on request.
</Info>

## Choose events

Subscribe only to the events you handle. Each event type is listed in the [event reference](/webhooks/events/overview).

You can also subscribe to a whole family with a wildcard such as `payment.*` or `expense.*`. A wildcard matches every current and future event whose name starts with that prefix. If an endpoint matches both an exact type and a wildcard, it still receives the event only once.

<Note>
  Wildcards pick up new event types automatically. Make sure your handler ignores event types it doesn't recognize, and returns `2xx` for them.
</Note>

## Build your handler

Your endpoint must:

1. Accept `POST` requests with `Content-Type: application/json`.
2. Read the **raw** request body before any JSON parsing.
3. [Verify the signature](/webhooks/verify-signatures) and return `401` if it fails.
4. Store the event, or put it on a queue, and return `200` straight away.
5. Process the event afterward, skipping any `id` you've already handled.

Keep the response body small. Pazy reads at most 64 KB of the response, and a larger response is treated as a failure. An empty body is best.

```javascript theme={null}
app.post('/webhooks/pazy', express.raw({ type: 'application/json' }), async (req, res) => {
  const rawBody = req.body.toString('utf-8');

  if (!isValidSignature(rawBody, req.headers)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(rawBody);
  await queue.add({ id: event.id, type: event.type, data: event.data }); // process later
  res.sendStatus(200);
});
```

`isValidSignature` is shown in full for Node.js, Python and Go on the [signature verification](/webhooks/verify-signatures) page.

## Test your endpoint

<Steps>
  <Step title="Start in the sandbox">
    Ask us for a sandbox endpoint first. Sandbox deliveries use the same payloads, headers and signing as production.
  </Step>

  <Step title="Expose your local server">
    To test from your machine, use a tunnel such as ngrok or Cloudflare Tunnel, and give us that HTTPS URL.
  </Step>

  <Step title="Trigger an event">
    Perform the action in the sandbox that raises your event, for example [marking an invoice as paid](/apis/mark-as-paid).
  </Step>

  <Step title="Check the headers and body">
    Confirm that your handler logs `X-Pazy-Webhook-Attempt: 1`, a valid signature, and the event `id` and `type`.
  </Step>
</Steps>

## Rotate your signing secret

Contact [customer-support@pazy.io](mailto:customer-support@pazy.io) to rotate a secret, for example if it leaked or someone with access left your team. Pazy signs each delivery with a single secret, and switches to the new one at the moment we apply the rotation.

To avoid failed verifications while you switch:

1. Agree a time with support for the rotation.
2. Before that time, update your handler to accept a signature that matches **either** the old or the new secret.
3. After the rotation, once deliveries verify against the new secret, remove the old secret.

Deliveries that fail verification during the switch are retried, and succeed once your handler accepts the new secret.


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