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

# Webhooks (merchant)

> Validate Pepay webhook signatures and handle durable event delivery.

## Overview

Merchant webhooks deliver canonical `PepayEvent` payloads to your backend. They mirror the same event types as merchant WebSockets, but are optimized for durable server-side processing.

## Authentication

Verify webhook signatures with `X-Pepay-Timestamp` + `X-Pepay-Signature` against the raw request body using your webhook secret(s).

## Request

```ts theme={null}
import express from 'express';
import { createWebhookVerifier } from 'pepay';

const verifier = createWebhookVerifier({
  secrets: [process.env.PEPAY_WEBHOOK_SECRET, process.env.PEPAY_WEBHOOK_SECRET_PREVIOUS].filter(Boolean),
  toleranceMs: 5 * 60 * 1000
});

const app = express();
app.post('/webhooks/pepay', express.raw({ type: 'application/json' }), async (req, res) => {
  const result = await verifier.verify({ rawBody: req.body, headers: req.headers });
  if (!result.valid) return res.status(400).send(result.reason || 'invalid_signature');

  const event = JSON.parse(req.body.toString('utf8'));
  console.log(event.type, event.id);
  return res.status(200).send('ok');
});
```

## Response

```json theme={null}
{
  "id": "evt_1700000000000-123",
  "object": "event",
  "type": "invoice.updated",
  "created": 1700000000,
  "data": {
    "object": {
      "object": "invoice",
      "id": "inv_123",
      "status": "paid"
    }
  }
}
```

## Errors

* Signature mismatch or stale timestamp: return `400`.
* Parsing/handler failures: return non-2xx to request retry.
* Do not return `2xx` until event processing is durably queued.

## Examples

* If you have multiple endpoints with different secrets, pass a `secrets` array and verify against any.
* Use `X-Pepay-Network-Environment` to route devnet vs mainnet secrets if you want strict separation.
* During rotation, keep the previous secret configured until all endpoints are updated.

## Dedupe and retries

* Delivery is at-least-once, so dedupe by `event.id` (or `X-Pepay-Event-ID`).
* Expect retries on non-2xx responses.
* Keep handlers idempotent.

## Relationship to WebSockets

* Merchant WebSockets deliver the same event types as merchant webhooks.
* The WebSocket stream includes replay + REST backfill for reliable realtime consumption.
* Webhooks remain best for server-side side effects and audit trails.

Next: [WebSockets (merchant)](/sdk/merchants/websockets)
