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

# Webhooks

> Get a server-to-server notification the moment a payment settles.

Webhooks are how you reliably learn a payment's outcome. Pass a `webhook_url`
when you [create a payment](/api-reference/create-payment) and Pesarc will POST a
JSON event to it as the payment progresses.

## Receiving events

```ts theme={null}
// POST /api/pesarc/webhook
export async function POST(req: Request) {
  const payload = await req.text();          // read the RAW body for verification
  const signature = req.headers.get("pesarc-signature") ?? "";

  if (!verify(payload, signature, process.env.PESARC_WEBHOOK_SECRET!)) {
    return new Response("bad signature", { status: 400 });
  }

  const event = JSON.parse(payload);
  switch (event.type) {
    case "payment.settled":
      await fulfil(event.data.reference);
      break;
    case "payment.expired":
    case "payment.failed":
      await cancel(event.data.reference);
      break;
  }
  return new Response("ok");
}
```

## Event shape

```json theme={null}
{
  "type": "payment.settled",
  "data": {
    "id": "pay_...",
    "reference": "order_1042",
    "amount": 50000,
    "currency": "NGN",
    "status": "settled",
    "metadata": { "orderId": "order_1042" }
  }
}
```

## Rules of thumb

<Check>Verify the signature against the **raw** request body before trusting an event.</Check>
<Check>Treat delivery as at-least-once — make your handler idempotent on `data.id`.</Check>
<Check>Respond `2xx` quickly; do slow work asynchronously so Pesarc doesn't retry.</Check>
<Check>Reconcile with [`GET /checkout/:id`](/api-reference/get-checkout) if you ever miss an event.</Check>

<Note>
  The signature header name and scheme are configured with your webhook secret in
  the **Developers** screen. If you haven't set a webhook secret yet, do that
  before relying on webhooks in production.
</Note>
