Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion api-features/webhooks-events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

## Event Categories

The Request Network webhook system emits **12 event types** across four categories:
The Request Network webhook system emits **13 event types** across five categories:
Comment thread
rodrigopavezi marked this conversation as resolved.

| Category | Events |
| --- | --- |
Expand All @@ -19,6 +19,7 @@
| **Processing** (crypto-to-fiat) | `payment.processing` (with `subStatus`) |
| **Request** | `request.recurring` |
| **Compliance / Bank** | `compliance.updated`, `payment_detail.updated` |
| **Secure Payment Page** (payer funnel) | `secure_payment.user_event` (with `userEvent`) |

The `.client_id` and `.checkout` variants are emitted in addition to the base `.confirmed` / `.partial` events when the request was created via a Client ID or as a checkout / secure payment, respectively. They include extra metadata (`clientId`, `origin`).

Expand Down Expand Up @@ -47,7 +48,7 @@
## Key Features

### Reliability
- **Idempotency support:** Use `x-request-network-delivery` header for duplicate detection

Check warning on line 51 in api-features/webhooks-events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-features/webhooks-events.mdx#L51

Did you really mean 'Idempotency'?
- **Delivery confirmation:** Monitor `x-request-network-retry-count` header to track attempts

### Security
Expand All @@ -57,7 +58,7 @@

### Development Tools
- **Test deliveries:** Fire test events via `POST /v1/webhook/test` (Auth API) — see [Webhooks reference](/api-reference/webhooks#testing)
- **ngrok integration:** Receive webhooks locally during development

Check warning on line 61 in api-features/webhooks-events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-features/webhooks-events.mdx#L61

Did you really mean 'ngrok'?
- **Comprehensive logging:** Request API logs all delivery failures with attempt details

## Common Use Cases
Expand All @@ -66,7 +67,8 @@
- **Order fulfillment:** Release goods or services immediately after payment confirmation
- **Subscription management:** Handle `request.recurring` for automatic billing renewals
- **Compliance workflows:** Update user permissions when `compliance.updated` shows KYC approval
- **Real-time dashboards:** Display live payment status using `payment.processing` subStatus values

Check warning on line 70 in api-features/webhooks-events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-features/webhooks-events.mdx#L70

Did you really mean 'subStatus'?
- **Payer-funnel visibility:** Track wallet connection and signature progress on the Secure Payment Page via `secure_payment.user_event`

## Implementation

Expand Down
45 changes: 45 additions & 0 deletions api-reference/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

| Event | Description | Context | Primary Use |
|-------|-------------|---------|-------------|
| `payment.confirmed` | Payment fully completed and settled | After blockchain confirmation | Complete fulfillment, release goods |

Check warning on line 65 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L65

Did you really mean 'blockchain'?
| `payment.partial` | Partial payment received for request | Installments, partial orders | Update balance, allow additional payments |
| `payment.failed` | Payment execution failed | Recurring payments, cross-chain transfers | Notify failure, retry logic, pause subscriptions |
| `payment.refunded` | Payment has been refunded to payer | Cross-chain payment failures, refund scenarios | Update order status, notify customer |
Expand All @@ -89,7 +89,7 @@

| Event | Description | Context | Primary Use |
|-------|-------------|---------|-------------|
| `payment.processing` | Crypto-to-fiat payment in progress | **subStatus values:** initiated, pending_internal_assessment, ongoing_checks, sending_fiat, fiat_sent, bounced | Track crypto-to-fiat payment status, update UI |

Check warning on line 92 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L92

Did you really mean 'subStatus'?

Check warning on line 92 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L92

Did you really mean 'pending_internal_assessment'?

Check warning on line 92 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L92

Did you really mean 'ongoing_checks'?

Check warning on line 92 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L92

Did you really mean 'sending_fiat'?

Check warning on line 92 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L92

Did you really mean 'fiat_sent'?

### Request Events

Expand All @@ -101,16 +101,40 @@

| Event | Description | Context | Primary Use |
|-------|-------------|---------|-------------|
| `compliance.updated` | KYC or agreement status changed | **kycStatus values:** not_started, pending, approved, rejected, retry_required<br/>**agreementStatus values:** not_started, pending, completed, rejected, failed | Update user permissions, notify status |

Check warning on line 104 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L104

Did you really mean 'kycStatus'?

Check warning on line 104 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L104

Did you really mean 'not_started'?

Check warning on line 104 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L104

Did you really mean 'retry_required'?

Check warning on line 104 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L104

Did you really mean 'agreementStatus'?

Check warning on line 104 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L104

Did you really mean 'not_started'?
| `payment_detail.updated` | Bank account verification status updated | States: approved, failed, pending | Enable fiat payments, update profiles |

### Secure Payment Page Events (payer funnel)

| Event | Description | Context | Primary Use |
|-------|-------------|---------|-------------|
| `secure_payment.user_event` | Payer progressed through a step of the Secure Payment Page | **userEvent values:** wallet_connected, payment_sent_to_wallet, payment_approved_in_wallet | Real-time payer-funnel visibility, drop-off analytics |

Check warning on line 111 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L111

Did you really mean 'userEvent'?

Check warning on line 111 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L111

Did you really mean 'wallet_connected'?

Check warning on line 111 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L111

Did you really mean 'payment_sent_to_wallet'?

Check warning on line 111 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L111

Did you really mean 'payment_approved_in_wallet'?

Sent to the same registered webhook endpoints as every other event — same Client ID scoping, `x-request-network-signature` HMAC verification, delivery headers, timeout, and 1s/5s/15s retry semantics described elsewhere on this page.

The `userEvent` field distinguishes the 3 funnel steps:

| `userEvent` | Meaning |
|-------------|---------|
| `wallet_connected` | The payer successfully connected a wallet on the secure payment page |
| `payment_sent_to_wallet` | The payment transaction was handed to the payer's wallet for signature |
| `payment_approved_in_wallet` | The payer approved/signed the payment in their wallet. `properties` includes the submission id (e.g. tx hash / user-operation hash) |

Check warning on line 121 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L121

Did you really mean 'tx'?

<Note>
`securePaymentToken` is the platform's correlation key, returned when the secure payment was created. `requestId` is present only when exactly one request is linked to the secure payment (see `requestIds` for the full list). `timestamp` is server-stamped on receipt. `occurredAt` and `properties` are **client-reported telemetry from the payer's browser** — useful for analytics, but not authoritative.

`secure_payment.user_event` is best-effort browser telemetry. Navigation, network errors, or browser extensions can prevent the API from receiving it. Webhook retries begin only after the API accepts the event. Do not treat an absent event as evidence that the payer did not take the step; use `payment.confirmed` for settlement and reconciliation.

When the Secure Payment Page includes wallet information in `properties`, it uses `wallet_address_hashed` rather than a raw wallet address.
</Note>

## Security Implementation

### Signature Verification
Every webhook includes an HMAC SHA-256 signature in the `x-request-network-signature` header:

```javascript
import crypto from "node:crypto";

Check warning on line 137 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L137

Did you really mean 'crypto'?

function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto
Expand Down Expand Up @@ -197,8 +221,8 @@
- `requestId` / `requestID`: Unique identifier for the payment request
- `paymentReference`: Short reference, also unique to a request, used to link payments to the request
- `timestamp`: ISO 8601 formatted event timestamp
- `paymentProcessor`: Either `request-network` (crypto) or `request-tech` (fiat)

Check warning on line 224 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L224

Did you really mean 'crypto'?
- `payerAddress`: Resolved payer wallet — the on-chain sender for plain direct payments, or the resolved payer for recurring and intent-based flows (Secure Payment Page, LiFi, Safe, ERC-4337, multicall). `null` when it cannot be determined. Included on `payment.confirmed` and `payment.partial` events (and their `.client_id` / `.checkout` variants).

Check warning on line 225 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L225

Did you really mean 'multicall'?
- `payerEoaAddress`: The payer's connected wallet address. It can differ from `payerAddress` when a smart account is used. `null` when unavailable. Included on `payment.confirmed` and `payment.partial` events (and their `.client_id` / `.checkout` variants).

### Payment Confirmed
Expand Down Expand Up @@ -303,6 +327,27 @@
}
```

### Secure Payment User Event
```json
{
"event": "secure_payment.user_event",
"userEvent": "payment_approved_in_wallet",
"securePaymentToken": "spt_3fk29ax7...",
"requestId": "01JD3E6JD46KY4KKV7X9V0MZ7W",
"requestIds": ["01JD3E6JD46KY4KKV7X9V0MZ7W"],
"orchestratorId": "orch_12345",
"occurredAt": "2026-08-05T14:03:21.512Z",
"timestamp": "2026-08-05T14:03:22.104Z",
"properties": {
"wallet_provider": "metamask",
"payment_submission_id": "0x6a4f...e21b",
"payment_submission_id_type": "evm_tx_hash",
"selected_source_chain": "base",
"payment_type": "single"
}
}
```

## Implementation Examples

For a complete working example, see [Webhook reconciliation](/use-cases/webhook-reconciliation) which implements webhook handling for payment notifications.
Expand Down Expand Up @@ -455,7 +500,7 @@
## Best Practices

### Error Handling
- **Implement idempotency:** Use delivery IDs to prevent duplicate processing

Check warning on line 503 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L503

Did you really mean 'idempotency'?
- **Graceful degradation:** Handle unknown event types without errors

### Performance
Expand All @@ -476,7 +521,7 @@
- Confirm the webhook is `active` via `GET /v1/webhook` (toggle with `PUT /v1/webhook/:id`)

### Debugging Tips
- Use ngrok request inspector to see raw webhook data

Check warning on line 524 in api-reference/webhooks.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

api-reference/webhooks.mdx#L524

Did you really mean 'ngrok'?
- Monitor retry counts in headers to identify issues
- Fire test deliveries via `POST /v1/webhook/test`

Expand Down
1 change: 1 addition & 0 deletions use-cases/quickstart.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Quickstart"
description: "End-to-end walkthrough — sign in to the Dashboard, create a payment destination, register a webhook, and create a hosted payment link."

Check warning on line 3 in use-cases/quickstart.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/quickstart.mdx#L3

Did you really mean 'walkthrough'?
---

This guide walks through the canonical flow for receiving payments via Request Network: creating a payment destination and Client ID in the Dashboard, registering a webhook for payment notifications, and creating a payment link. Every other use-case page links back to specific steps here.
Expand Down Expand Up @@ -235,7 +235,7 @@
| Header | Description |
| --- | --- |
| `x-request-network-signature` | HMAC-SHA256 of the raw JSON body, signed with your webhook secret |
| `x-request-network-delivery` | Unique delivery ID — use as an idempotency key |

Check warning on line 238 in use-cases/quickstart.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/quickstart.mdx#L238

Did you really mean 'idempotency'?
| `x-request-network-retry-count` | Retry attempt number (`0`–`3`) |
| `x-request-network-test` | `true` only for test deliveries via `/v1/webhook/test` |

Expand Down Expand Up @@ -293,7 +293,7 @@
| `totalAmountPaid` | Cumulative amount paid so far |
| `expectedAmount` | The total amount expected |
| `txHash` | On-chain transaction hash |
| `network` | The blockchain network |

Check warning on line 296 in use-cases/quickstart.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/quickstart.mdx#L296

Did you really mean 'blockchain'?
| `currency` | The token used for payment |

### All supported webhook events
Expand All @@ -308,10 +308,11 @@
| `payment.partial.checkout` | Secure-payment-scoped variant of `payment.partial` |
| `payment.failed` | Payment failed |
| `payment.refunded` | Payment refunded |
| `payment.processing` | Offramp processing started |

Check warning on line 311 in use-cases/quickstart.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/quickstart.mdx#L311

Did you really mean 'Offramp'?
| `request.recurring` | A recurring request fired |
| `payment_detail.updated` | Payment detail metadata changed |
| `compliance.updated` | Compliance status changed |
| `secure_payment.user_event` | Payer progressed through a Secure Payment Page step (`userEvent`: `wallet_connected`, `payment_sent_to_wallet`, `payment_approved_in_wallet`) — funnel telemetry, not a settlement signal |

## Step 5: Create a Secure Payment (Payment Link)

Expand Down
89 changes: 54 additions & 35 deletions use-cases/webhook-reconciliation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

**Audience:** any backend integrating Request Network where payment events drive state changes downstream.

## The 12 events
## The 13 events

| Category | Event | When it fires |
| --- | --- | --- |
Expand All @@ -21,10 +21,11 @@
| | `payment.partial.client_id` | Client ID-scoped partial |
| Payment (Checkout) | `payment.confirmed.checkout` | Same as `payment.confirmed`, request originated from a Secure Payment link |
| | `payment.partial.checkout` | Secure Payment-scoped partial |
| Processing | `payment.processing` | Crypto-to-fiat offramp in progress (with detailed `subStatus`) |

Check warning on line 24 in use-cases/webhook-reconciliation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/webhook-reconciliation.mdx#L24

Did you really mean 'offramp'?
| Request | `request.recurring` | A new recurring billing cycle fired |
| Compliance | `compliance.updated` | KYC or agreement status changed |
| Bank details | `payment_detail.updated` | Bank account verification status changed |
| Secure Payment Page | `secure_payment.user_event` | Payer progressed through a step of the Secure Payment Page (`userEvent`: `wallet_connected`, `payment_sent_to_wallet`, `payment_approved_in_wallet`) — funnel telemetry, **not** a settlement signal |

For the full payload schemas, see the [Webhooks reference](/api-reference/webhooks).

Expand All @@ -48,15 +49,13 @@

## Handler — reference implementation

A signature-verifying Express handler with idempotency. Hardened for production: verifies against the **raw** body, uses constant-time comparison, deduplicates on `x-request-network-delivery`.
A signature-verifying Express handler. It verifies against the **raw** body, uses constant-time comparison, passes the delivery ID to business handlers as their idempotency key, and lets Request Network retry a failed handler.

Check warning on line 52 in use-cases/webhook-reconciliation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/webhook-reconciliation.mdx#L52

Did you really mean 'idempotency'?

```typescript
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
import { Redis } from "ioredis";

const app = express();
const redis = new Redis(process.env.REDIS_URL!);
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;

app.post(
Expand All @@ -65,7 +64,6 @@
async (req, res) => {
const signature = req.headers["x-request-network-signature"] as string;
const deliveryId = req.headers["x-request-network-delivery"] as string;
const isTest = req.headers["x-request-network-test"] === "true";

if (!signature || !deliveryId) {
return res.status(400).send("missing headers");
Expand All @@ -86,39 +84,26 @@
return res.status(401).send("invalid signature");
}

// 2. Idempotency: dedupe on delivery ID
const claim = await redis.set(
`webhook:${deliveryId}`,
"1",
"EX",
86400, // 24h
"NX",
);
if (!claim && !isTest) {
return res.status(200).send("duplicate, ignored");
}

// 3. Parse and route
// 2. Parse and route. Each business operation uses deliveryId as an
// idempotency key in its own durable store.
const event = JSON.parse(req.body.toString("utf8"));

try {
await handleEvent(event);
await handleEvent(event, deliveryId);
return res.status(200).send("ok");
} catch (err) {
// Return non-2xx to trigger retry (1s, 5s, 15s — up to 3 retries)
console.error("handler failed", err);
return res.status(500).send("handler error");
}

res.status(200).send("ok");
},
);

async function handleEvent(event: any) {
async function handleEvent(event: any, deliveryId: string) {
switch (event.event) {
case "payment.confirmed":
case "payment.confirmed.client_id":
case "payment.confirmed.checkout":
await markOrderPaid(event.requestId, event.txHash);
await markOrderPaid(event.requestId, event.txHash, deliveryId);
Comment thread
greptile-apps[bot] marked this conversation as resolved.
break;

case "payment.partial":
Expand All @@ -128,32 +113,46 @@
event.requestId,
event.amount,
event.totalAmountPaid,
deliveryId,
);
break;

case "payment.failed":
await flagFailedPayment(event.requestId);
await flagFailedPayment(event.requestId, deliveryId);
break;

case "request.recurring":
await onRecurringInvoice(event.originalRequestId, event.requestId);
await onRecurringInvoice(event.originalRequestId, event.requestId, deliveryId);
break;

case "compliance.updated":
await syncKycStatus(event.clientUserId, event.kycStatus);
await syncKycStatus(event.clientUserId, event.kycStatus, deliveryId);
break;

// Payer-funnel telemetry from the Secure Payment Page. Never reconcile
// money off this — a payer can approve in their wallet and still have the
// transaction fail on-chain. Wait for payment.confirmed for settlement.
case "secure_payment.user_event":
await recordFunnelStep(
event.securePaymentToken,
event.userEvent,
deliveryId,
);
break;
Comment thread
greptile-apps[bot] marked this conversation as resolved.

// ... others
}
}
```

Webhook delivery is at least once, not exactly once. Each business operation must atomically record the delivery ID with the state it changes, then make a repeat delivery a successful no-op. If an operation calls another service, pass the delivery ID as that service's idempotency key too. A process can fail after a side effect but before it returns `200`.

Check warning on line 148 in use-cases/webhook-reconciliation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/webhook-reconciliation.mdx#L148

Did you really mean 'idempotency'?

## Headers reference

| Header | Description |
| --- | --- |
| `x-request-network-signature` | HMAC-SHA256 of the raw JSON body, hex-encoded |
| `x-request-network-delivery` | ULID — use as idempotency key |

Check warning on line 155 in use-cases/webhook-reconciliation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/webhook-reconciliation.mdx#L155

Did you really mean 'idempotency'?
| `x-request-network-retry-count` | `0`–`3`, current retry attempt |
| `x-request-network-test` | `true` only for `/v1/webhook/test` deliveries |

Expand All @@ -170,22 +169,42 @@

## Common patterns

### Idempotency

Check warning on line 172 in use-cases/webhook-reconciliation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/webhook-reconciliation.mdx#L172

Did you really mean 'Idempotency'?

The same `payment.confirmed` event might arrive twice (network blip, retry overlap). Dedupe on `x-request-network-delivery`.

### Routing by Client ID
The same `payment.confirmed` event might arrive twice (network blip, retry overlap). Use `x-request-network-delivery` as the idempotency key. Record it atomically with the business update in your durable store; do not use a check-then-act cache lookup, because overlapping deliveries can both pass the check.

Check warning on line 174 in use-cases/webhook-reconciliation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/webhook-reconciliation.mdx#L174

Did you really mean 'idempotency'?

If your platform has many merchants, give each their own Client ID. The webhook payload includes `clientId` so you can route events to the right tenant.
For a local database update, add a `webhook_deliveries` table with a unique `delivery_id` column, then insert that ID in the same transaction as the business update:

```typescript
async function markOrderPaid(requestId: string, txHash: string) {
const order = await db.orders.findOne({ where: { requestId } });
if (!order) return; // not ours
await db.orders.update({ where: { id: order.id }, data: { paidAt: new Date(), txHash } });
async function markOrderPaid(
requestId: string,
txHash: string,
deliveryId: string,
) {
await db.transaction(async (tx) => {
const order = await tx.orders.findOne({ where: { requestId } });
if (!order) return; // not ours

const claim = await tx.execute(
`INSERT INTO webhook_deliveries (delivery_id)
VALUES ($1)
ON CONFLICT (delivery_id) DO NOTHING`,
[deliveryId],
);
if (claim.rowCount === 0) return; // already applied

await tx.orders.update({
where: { id: order.id },
data: { paidAt: new Date(), txHash },
});
});
}
```

### Routing by Client ID

If your platform has many merchants, give each their own Client ID. The webhook payload includes `clientId` so you can route events to the right tenant.

### Slack alerts on failure

```typescript
Expand All @@ -201,7 +220,7 @@

### Crypto-to-fiat status tracking

The `payment.processing` event includes a `subStatus` field that progresses through `initiated → pending_internal_assessment → ongoing_checks → sending_fiat → fiat_sent`. Surface this in your UI so the payee sees real-time offramp progress.

Check warning on line 223 in use-cases/webhook-reconciliation.mdx

View check run for this annotation

Mintlify / Mintlify Validation (requestnetwork) - vale-spellcheck

use-cases/webhook-reconciliation.mdx#L223

Did you really mean 'offramp'?

## Local development

Expand Down