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

# Partner Webhook

> Server-to-server delivery of transaction events

If you provide a `webhook_url` at onboarding, Proof POSTs a normalized transaction event to that URL on every status change.

This channel is optional — many partners rely on WebSocket and polling only. The webhook is most useful when your backend needs to react server-side without keeping a persistent connection open.

## Delivery model

| Attribute            | Value                                                                                                                                                         |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP method          | `POST`                                                                                                                                                        |
| Content-Type         | `application/json`                                                                                                                                            |
| Trigger              | Every status change for any of your transactions                                                                                                              |
| Delivery             | Best-effort, fire-and-forget                                                                                                                                  |
| Retries              | None on a single status change. Proof relies on internal retries to redeliver, which can result in the same `merchant_transaction_id` arriving multiple times |
| Authoritative source | No — pair the webhook with polling or WebSocket for state your business depends on                                                                            |

## Payload

```json theme={null}
{
  "merchant_transaction_id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "buy",
  "status": "completed",
  "currency": "USDT",
  "network": "TRC20",
  "crypto_amount": "99.50",
  "fiat_currency": "EUR",
  "fiat_amount": "100.00",
  "created_at": "2026-04-01T10:00:00Z",
  "updated_at": "2026-04-01T10:03:45Z"
}
```

| Field                     | Type   | Notes                                                                   |
| ------------------------- | ------ | ----------------------------------------------------------------------- |
| `merchant_transaction_id` | string | Stable across retries; use as your idempotency key                      |
| `type`                    | string | `"buy"` or `"sell"`                                                     |
| `status`                  | string | Normalized: `pending`, `processing`, `completed`, `failed`, `cancelled` |
| `currency`                | string | Crypto currency                                                         |
| `network`                 | string | Blockchain network                                                      |
| `crypto_amount`           | string | May be empty before `processing`                                        |
| `fiat_currency`           | string | Fiat currency                                                           |
| `fiat_amount`             | string | Fiat amount                                                             |
| `created_at`              | string | ISO 8601 timestamp (UTC)                                                |
| `updated_at`              | string | ISO 8601 timestamp (UTC) of this status change                          |

The status enum matches [`GET /widget/transactions/{id}`](/on-off-ramp/api-transaction) and the WebSocket `tx.update` event.

## Handler expectations

<Check>
  Respond with any `2xx` status code as quickly as possible. Heavy work should happen asynchronously after acknowledgement.
</Check>

<Check>
  Be idempotent on `merchant_transaction_id` + `status`. Treat duplicate deliveries as no-ops.
</Check>

<Check>
  Treat the webhook as advisory. If the webhook stops arriving for a transaction you care about, fall back to polling or WebSocket — do not block business logic on the webhook alone.
</Check>

## Example handler (Express)

```javascript theme={null}
app.post("/proof-webhook", express.json(), async (req, res) => {
  const event = req.body;

  // ACK first
  res.sendStatus(200);

  // Process asynchronously, idempotently
  await reconcileTransaction(event.merchant_transaction_id, event.status, event);
});
```

## Updating the webhook URL

Contact the Proof team to add, change, or remove your `webhook_url`. There is no self-service endpoint.
