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

# Webhooks

> Outgoing webhook format, delivery semantics, and signature verification.

When an invoice changes status, PawPayments POSTs an invoice snapshot (JSON)
to the merchant's `notify_url` (per-invoice override) or to the merchant-level
`callback_url`. Delivery is **automatic** and **idempotent** — every merchant
receives at most one webhook per terminal transition of each invoice.

## Delivery guarantees

* **Automatic.** Fires on every status change of an invoice created via
  `POST /api/v2/invoices` (and on auto-created invoices bound to permanent
  addresses).
* **Persistent retries.** Delivery attempts run as a durable background job,
  so they survive process restarts.
  On 2xx → done. On 4xx (except `408`/`425`/`429`) → give up immediately
  (merchant is presumed misconfigured). Otherwise the job is retried with
  exponential backoff: **30s, 1m, 2m, 5m, 10m, 20m, 40m, 80m, 160m** —
  up to **10 attempts** total (≈5h wall-clock).
* **URL safety.** Each attempt DNS-resolves the callback and refuses to
  deliver if the host is private, loopback, link-local, or unresolvable.
  These refusals are logged as attempts too (with a human-readable reason).
* **Source IPs.** All webhooks originate from the subnet
  `94.249.204.0/24`. Allowlist this range if your endpoint is
  firewalled or restricted to known senders.
* **Audit log.** Every delivery attempt is recorded: URL, status code,
  response body (first 500 chars), attempt number, and timestamp. Inspect
  via `GET /api/v2/notifications` or replay a specific invoice on demand
  via `POST /api/v2/invoices/{id}/notify`.

## Headers

| Header | Purpose |
| - | - |
| `Content-Type: application/json` | Body is a single JSON object. |
| `User-Agent: Mozilla/5.0 (compatible; PawWebhook/2.0; ...)` | Identifies the sender. |
| `X-Paw-Signature` | HMAC-SHA256 of the **exact raw body** keyed with the merchant's API key (hex-encoded). |
| `X-Paw-Sandbox: true` | Present only on sandbox deliveries. |

## Payload

The payload is the invoice shape returned by `GET /api/v2/invoices/{id}`
(see the API Reference for the canonical schema), plus `external_id`, which the
read endpoints withhold. Authenticity is established exclusively by the
`X-Paw-Signature` header — there is no `signature` field inside the JSON body.

Key fields for merchant processing:

| Field | Meaning |
| - | - |
| `order_id` | Unique 24-char hex id of the invoice. |
| `external_id` | Public checkout id (UUID) used in the hosted-checkout URL. Delivered here and nowhere else: it is also the credential a payer presents to refund their own flagged payment, so `GET /api/v2/invoices` and `GET /api/v2/invoices/{id}` do not return it. Keep the `payment_url` from the create response if you need the link again. |
| `status` | One of `confirming`, `partially_paid`, `paid_over`, `success`, `failed`, `high_risk`, `cancelled`. `cancelled` fires only when a `partially_paid` + `billing_type=STATIC` invoice crosses `expires_at` without a top-up — the partial credit on the merchant balance is **kept** and the webhook lets you reconcile the final under-paid state. Webhooks are NOT delivered for `expired` (TTL passed with no successful credit — `created`/`waiting` rows, including sub-\$2 tiny-payment deferrals that AML never enqueued) or `refunded` (admin-driven refund) — those state changes are visible via `GET /api/v2/invoices/{id}` but never push. |
| `billing_type` | `STATIC` or `VARY` — see [Billing types](./billing-type). |
| `asset` / `type` | Crypto asset (`usdt_tron`, `eth_eth`, …) and `native`/`token`. |
| `amount` / `initial_amount` | Requested crypto amount (immutable). |
| `fiat_amount` / `initial_fiat_amount` | What the deposit was worth when it was credited (live rate, net of commission) / the amount the invoice was issued for. On a settled invoice `fiat_amount` can land a fraction under `initial_fiat_amount` — settle against `initial_fiat_amount`, or the larger of the two on `paid_over`. |
| `address_to` / `address_from` | Deposit address and (when known) sender. |
| `received_amount` / `txid` | Last on-chain payment details. |
| `expires_at` | Unix timestamp when the invoice stops accepting deposits. |
| `processed_at` | Unix timestamp of the terminal transition. |
| `metadata` | Arbitrary key/value dict passed on invoice creation. |

A finalised webhook (`success`, `paid_over`, `failed`, `high_risk`,
`cancelled`) carries the authoritative payment state — treat it as the
source of truth and disregard any earlier `confirming` / `partially_paid`
deliveries for the same invoice.

## Verification (Node.js)

```ts theme={null}
import crypto from "node:crypto";

function verify(rawBody: Buffer, signatureHeader: string, apiKey: string) {
  const expected = crypto
    .createHmac("sha256", apiKey)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader),
  );
}
```

**Important:** verify against the **raw request body** before JSON-parsing.
Any whitespace / key-reordering will produce a different signature.

## Verification (PHP)

```php theme={null}
function verify(string $rawBody, string $signatureHeader, string $apiKey): bool {
    $expected = hash_hmac('sha256', $rawBody, $apiKey);
    return hash_equals($expected, $signatureHeader);
}
```

## Replay and introspection

* `POST /api/v2/invoices/{id}/notify` — re-fires the webhook for the invoice
  on demand (useful for recovery or local-dev testing).
* `GET /api/v2/notifications` — paginated audit log of every delivery
  attempt (successful, retrying, or given up).

## Sandbox webhooks

Sandbox deliveries are identical in shape, with two markers: the payload carries
`"sandbox": true` and the request carries `X-Paw-Sandbox: true`. The signature
is keyed with the **sandbox** API key, so a receiver verifying against the live
key will reject a sandbox webhook — verify with whichever key that environment
is configured with. See [Sandbox](/sandbox) for driving invoices through their
lifecycle by hand.


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