diff --git a/api-features/secure-payment-integration-guide.mdx b/api-features/secure-payment-integration-guide.mdx
index e81785e..7b08608 100644
--- a/api-features/secure-payment-integration-guide.mdx
+++ b/api-features/secure-payment-integration-guide.mdx
@@ -1,259 +1,332 @@
---
-title: "Secure Payment Integration Guide"
-description: "Integrate a secure payment experience via a secured link (redirect)"
+title: "Creating Payment Destinations and Payment Links"
+description: "End-to-end flow: wallet sign-in, payment destinations, client IDs, webhooks, and secure payment links."
---
## Overview
-This guide shows how to integrate Secure Payment Pages in a production-ready way.
+This guide walks through the end-to-end flow for receiving payments via Request Network: creating a payment destination in Dashboard, generating a Client ID, setting up webhooks, and creating a secure payment link.
+
+The flow involves four services:
+
+1. **Dashboard** (`dashboard.request.network`) - Sign in with wallet and create payment destinations
+2. **Auth API** (`auth.request.network`) - Create Client IDs
+3. **Portal** (`portal.request.network`) - Set up webhooks
+4. **Request API** (`api.request.network`) - Create secure payments (payment links)
+
+```text
+┌──────────────────────┐
+│ 1. Sign in with │ dashboard.request.network
+│ wallet │
+└──────────┬───────────┘
+ │
+ ▼
+┌──────────────────────┐
+│ 2. Create Payee │ dashboard.request.network
+│ Destination │
+└──────────┬───────────┘
+ │
+ ▼
+┌──────────────────────┐
+│ 3. Create Client ID │ auth.request.network/open-api
+│ (Auth API) │
+└──────────┬───────────┘
+ │
+ ▼
+┌──────────────────────┐
+│ 4. Set up Webhook │ portal.request.network
+└──────────┬───────────┘
+ │
+ ▼
+┌──────────────────────┐
+│ 5. Create Secure │ api.request.network/open-api
+│ Payment (link) │
+└──────────────────────┘
+```
-The recommended pattern is:
-1. Create secure payment links with `POST /v2/secure-payments`
-2. Store returned `requestIds` in your system
-3. Redirect the payer to the secure page
-4. Use webhooks as the source of truth for payment status updates
+
+Your wallet session cookie (`session_token`) is shared across Dashboard, Portal, Auth API, and Request API docs. Signing in once on Dashboard gives access to the others.
+
## Prerequisites
-Before you integrate, make sure you have:
-
-- An API key or a Client ID linked to your integration domain
-- A webhook endpoint configured in Request Portal
-- Your webhook signing secret stored securely on your backend
-
-For setup details, see:
-- [Authentication](/api-reference/authentication)
-- [Webhooks](/api-reference/webhooks)
-
-## Quick start
-
-
-
- Call `POST /v2/secure-payments` and store the returned `requestIds`.
-
-
- ```bash cURL
- curl -X POST "https://api.request.network/v2/secure-payments" \
- -H "x-api-key: YOUR_API_KEY" \
- -H "Content-Type: application/json" \
- -d '{
- "requests": [
- {
- "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
- "amount": "10",
- "invoiceCurrency": "USDC-base",
- "paymentCurrency": "USDC-base"
- }
- ]
- }'
- ```
-
-
-
- ```json 201 Created
+- An EVM wallet (for example MetaMask) that will receive payments
+- Ability to sign authentication messages with that wallet
+- A server endpoint for receiving webhooks
+
+## Step 1: Sign in with wallet
+
+1. Go to [dashboard.request.network](https://dashboard.request.network/).
+2. Connect your EVM wallet and sign the authentication message.
+3. Keep this session active for the next steps.
+
+
+Wallet sessions expire after around 15 minutes. If your session expires, sign in again from Dashboard.
+
+
+## Step 2: Create a payment destination
+
+A payment destination registers where you receive payments, by linking your wallet address to a token on a chain.
+
+1. In Dashboard, navigate to payment destination setup.
+2. Select chain and token.
+3. Confirm creation.
+
+Dashboard returns a `destinationId` (also shown as `humanReadableInteropAddress`). Save it for Step 5.
+
+The `destinationId` follows ERC-7828 format:
+
+```text
+{walletAddress}@eip155:{chainId}#{checksum}
+```
+
+### Supported chains and tokens
+
+| Network | Chain ID | USDC | USDT |
+| --- | --- | --- | --- |
+| Ethereum | `1` | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | `0xdAC17F958D2ee523a2206206994597C13D831ec7` |
+| Arbitrum One | `42161` | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` | `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9` |
+| Optimism | `10` | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` | `0x94b008aA00579c1307B0EF2c499aD98a8ce58e58` |
+| Base | `8453` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2` |
+| Polygon | `137` | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` | `0xc2132D05D31c914a87C6611C10748AEb04B58e8F` |
+| BSC | `56` | `0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d` | `0x55d398326f99059ff775485246999027b3197955` |
+
+**Testnet**
+
+| Network | Chain ID | Tokens |
+| --- | --- | --- |
+| Sepolia | `11155111` | FAU `0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C`, USDC `0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238`, USDT `0xF046b3CA5ae2879c6bAcC4D42fAF363eE8379F78` |
+
+## Step 3: Create a Client ID
+
+A Client ID identifies your application and is required for browser-origin payment-link creation.
+
+1. Open [Auth API docs: POST /v1/client-ids](https://auth.request.network/open-api/#tag/client-ids/POST/v1/client-ids).
+2. Reuse your active wallet session.
+3. Submit:
+
+```json
+{
+ "label": "My Client ID",
+ "allowedDomains": [
+ "https://mydomain.com"
+ ]
+}
+```
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `label` | `string` | Yes | Human-readable name (1-100 chars) |
+| `allowedDomains` | `string[]` | Yes | Origins allowed to use this client ID (1-10) |
+| `feePercentage` | `string` | No | Fee percentage to charge (0-100) |
+| `feeAddress` | `string` | No | Wallet address to receive fees |
+| `operatorWalletAddress` | `string` | No | Operator wallet address |
+
+
+`allowedDomains` must use `https://` (except `localhost`, `127.0.0.1`, or `::1`, which can use `http://`). No path/query/fragment.
+
+
+**Example response**
+
+```json 201 Created
+{
+ "id": "01KJBN4KR5PFG4NAQG60EHR2Y0",
+ "clientId": "cli_nz1bj41szV2fvjm9pbxdIhro3ld4x4",
+ "label": "My Client ID",
+ "allowedDomains": [
+ "https://mydomain.com"
+ ],
+ "feePercentage": null,
+ "feeAddress": null,
+ "operatorWalletAddress": null,
+ "defaultPreApprovalExpiry": null,
+ "defaultAuthorizationExpiry": null,
+ "status": "active",
+ "createdAt": "2026-03-02T19:42:37 GMT+0000"
+}
+```
+
+Save `clientId` for Step 5.
+
+## Step 4: Set up a webhook
+
+Webhooks provide payment notifications (`payment.confirmed`, `payment.partial`, and others) without polling.
+
+1. Go to [portal.request.network/dashboard/webhooks](https://portal.request.network/dashboard/webhooks).
+2. Add your endpoint URL (for example `https://mydomain.com/webhook`).
+3. Save the webhook signing secret.
+
+The webhook is scoped to your platform (wallet). Payment links created under your platform trigger this webhook regardless of client ID; payloads include `clientId` so you can route events.
+
+### Signature headers
+
+| Header | Description |
+| --- | --- |
+| `x-request-network-signature` | HMAC-SHA256 of the request body, signed with your webhook secret |
+| `x-request-network-delivery` | Unique delivery ID |
+| `x-request-network-retry-count` | Retry attempt number |
+
+Verify by computing `HMAC-SHA256(raw_request_body, webhook_secret)` and comparing to `x-request-network-signature`.
+
+For full webhook setup, events, retries, security, and payload docs, see [Webhooks](/api-reference/webhooks).
+
+## Step 5: Create a secure payment (payment link)
+
+Now create the payment link using:
+- `destinationId` from Step 2
+- `clientId` from Step 3 in the `x-client-id` header
+
+1. Open [Request API docs: POST /v2/secure-payments](https://api.request.network/open-api/#tag/v2secure-payment/POST/v2/secure-payments).
+2. Set header: `x-client-id: `.
+3. Submit:
+
+```json
+{
+ "requests": [
{
- "requestIds": [
- "01e273ecc29d4b526df3a0f1f05ffc59372af8752c2b678096e49ac270416a7cdb"
- ],
- "securePaymentUrl": "https://secure.request.network/?token=01ABC123DEF456GHI789JKL",
- "token": "01ABC123DEF456GHI789JKL"
+ "destinationId": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7@eip155:11155111#1f969856:0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C",
+ "amount": "1"
}
- ```
-
-
-
-
- Store at least:
- - your internal metadata
- - returned `requestIds`
- - `token`
- - `securePaymentUrl`
-
- This mapping lets you reconcile webhook events back to your internal records.
-
-
-
- Redirect in the same tab or open the secure URL in a new tab.
-
-
-
- Handle payment events from webhooks and update your order/payment state from those events.
-
-
-
-## Integration pattern: generated URL + redirect
-
-### Backend example (Node.js/Express)
-
-```javascript server.js
-import express from "express";
-
-const app = express();
-app.use(express.json());
-
-app.post("/api/checkout/secure-payment", async (req, res) => {
- const { orderId, payee, amount, currencyId } = req.body;
-
- const apiResponse = await fetch("https://api.request.network/v2/secure-payments", {
- method: "POST",
- headers: {
- "x-api-key": process.env.REQUEST_API_KEY,
- "content-type": "application/json",
- },
- body: JSON.stringify({
- requests: [
- {
- payee,
- amount,
- invoiceCurrency: currencyId,
- paymentCurrency: currencyId,
- },
- ],
- }),
- });
-
- if (!apiResponse.ok) {
- const errorBody = await apiResponse.text();
- return res.status(apiResponse.status).json({ error: errorBody });
- }
-
- const securePayment = await apiResponse.json();
-
- // Persist in your DB
- // Example payload:
- // {
- // orderId,
- // requestIds: securePayment.requestIds,
- // token: securePayment.token,
- // securePaymentUrl: securePayment.securePaymentUrl,
- // status: "pending"
- // }
-
- return res.status(200).json({
- orderId,
- securePaymentUrl: securePayment.securePaymentUrl,
- });
-});
+ ]
+}
+```
+
+### Constructing `destinationId`
+
+`destinationId` is:
+
+```text
+{humanReadableInteropAddress}:{tokenAddress}
```
-### Frontend redirect examples
+Example:
-
-```javascript Same tab
-window.location.href = securePaymentUrl;
+```text
+0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7@eip155:11155111#1f969856:0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C
+└──────────── humanReadableInteropAddress ─────────────────┘ └──────────── tokenAddress ──────────────────────┘
```
-```javascript New tab
-window.open(securePaymentUrl, "_blank", "noopener,noreferrer");
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+| `requests` | `array` | Yes | Array of payment request items |
+| `requests[].destinationId` | `string` | Yes | Composite ID `humanReadableInteropAddress:tokenAddress` |
+| `requests[].amount` | `string` | Yes | Human-readable amount (for example `"10"`) |
+| `feePercentage` | `string` | No | Fee percentage 0-100; if set, `feeAddress` is required |
+| `feeAddress` | `string` | No | Fee recipient address |
+
+**Example response**
+
+```json 201 Created
+{
+ "requestIds": [
+ "01de2a889ee629c15b71b5d7964e3a7e87638c886be75bf1b9d2c1fbe64cf855fb"
+ ],
+ "securePaymentUrl": "https://pay.request.network/?token=01KJRA0M9QG8MA4X887908T8A4",
+ "token": "01KJRA0M9QG8MA4X887908T8A4"
+}
```
-
-
-## Payment status updates with webhooks
-
-Use webhook events as your payment status source of truth.
-
-Typical mapping:
-- `payment.confirmed` -> mark order as paid
-- `payment.partial` -> mark order as partially paid
-- `payment.failed` -> mark order as failed
-
-### Webhook handler example (signature verification + reconciliation)
-
-```javascript webhook.js
-import crypto from "node:crypto";
-import express from "express";
-
-const app = express();
-
-app.use(
- express.raw({
- type: "application/json",
- verify: (req, _res, buf) => {
- req.rawBody = buf;
- },
- }),
-);
-
-app.post("/webhooks/request", async (req, res) => {
- const signature = req.headers["x-request-network-signature"];
- const secret = process.env.REQUEST_WEBHOOK_SECRET;
-
- const expectedSignature = crypto
- .createHmac("sha256", secret)
- .update(req.rawBody)
- .digest("hex");
-
- if (!signature) {
- return res.status(401).json({ error: "Missing signature" });
- }
-
- try {
- const isValid = crypto.timingSafeEqual(
- Buffer.from(signature),
- Buffer.from(expectedSignature),
- );
- if (!isValid) {
- return res.status(401).json({ error: "Invalid signature" });
- }
- } catch {
- return res.status(401).json({ error: "Invalid signature format" });
- }
- const event = JSON.parse(req.rawBody.toString("utf8"));
- const requestId = event.requestId || event.requestID;
+Share `securePaymentUrl` with payer.
+
+
+Secure payment links expire after 7 days or once paid.
+
+
+For full endpoint schema and errors, see [Secure Payments API Reference](/api-reference/secure-payments).
+
+## Step 6: Check payment status (optional)
- // Find internal record by requestId in your DB, then update order status.
- // Example:
- // const checkout = await db.findCheckoutByRequestId(requestId)
- // if (event.event === "payment.confirmed") await db.markPaid(checkout.orderId)
+Webhooks are recommended as source of truth, but you can also poll by `requestId`.
- return res.status(200).json({ received: true });
-});
+Endpoint:
+
+```text
+GET https://api.request.network/v2/request/{requestId}
```
-## Expiry handling
+Programmatic calls can use the same credentials model from [Authentication](/api-reference/authentication).
+
+**Paid**
-Secure payment links expire after one week by default.
+```json 200 OK
+{
+ "hasBeenPaid": true,
+ "requestId": "01de2a889ee629c15b71b5d7964e3a7e87638c886be75bf1b9d2c1fbe64cf855fb",
+ "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
+ "isListening": false,
+ "txHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
+}
+```
-If a payer opens an expired link, create a new secure payment link and redirect again.
+**Unpaid**
+
+```json 200 OK
+{
+ "hasBeenPaid": false,
+ "requestId": "01de2a889ee629c15b71b5d7964e3a7e87638c886be75bf1b9d2c1fbe64cf855fb",
+ "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
+ "isListening": true,
+ "txHash": null
+}
+```
+
+| Field | Type | Description |
+| --- | --- | --- |
+| `hasBeenPaid` | `boolean` | Whether the request has been fully paid |
+| `requestId` | `string` | Request ID |
+| `payee` | `string` | Payee wallet address |
+| `isListening` | `boolean` | Whether the system is still listening for payment |
+| `txHash` | `string \| null` | Payment tx hash |
+
+## Quick reference
+
+| Step | Action | Where |
+| --- | --- | --- |
+| 1 | Sign in with wallet | [dashboard.request.network](https://dashboard.request.network/) |
+| 2 | Create payment destination | [dashboard.request.network](https://dashboard.request.network/) |
+| 3 | Create Client ID | [auth.request.network/open-api](https://auth.request.network/open-api/#tag/client-ids/POST/v1/client-ids) |
+| 4 | Set up webhook | [portal.request.network/dashboard/webhooks](https://portal.request.network/dashboard/webhooks) |
+| 5 | Create payment link | [api.request.network/open-api](https://api.request.network/open-api/#tag/v2secure-payment/POST/v2/secure-payments) |
+| 6 | Check payment status | [api.request.network/open-api](https://api.request.network/open-api) or webhook |
## Troubleshooting
-
- - Verify your `x-api-key` or `x-client-id` header
- - If using Client ID in browser, verify the request origin is in allowed domains
+
+ - Confirm `x-client-id` is set correctly
+ - Confirm your browser `Origin` is included in `allowedDomains`
+ - Re-authenticate if your wallet session expired
-
- - The token may be expired
- - Create a fresh secure payment link and retry
+
+ - Use raw request body bytes for HMAC computation
+ - Use your exact webhook secret from Portal
+ - Compare signatures using a timing-safe method
-
- - Payment is already completed
- - Show a paid/completed state in your app instead of retrying payment
+
+ - Links expire after 7 days
+ - A paid link cannot be reused
+ - Create a new secure payment link for a fresh attempt
-
- - Verify HMAC signature validation uses raw request body
- - Ensure your endpoint returns `2xx` after successful processing
- - Confirm your DB lookup maps incoming `requestId`/`requestID` to stored request IDs
+
+ - Verify webhook URL is publicly reachable over HTTPS
+ - Return `2xx` quickly from your endpoint
+ - Check retry headers and delivery logs
## Related docs
-
- Full request and response schema details.
+
+ API key and Client ID setup.
-
- Event types, signing, retries, and payload details.
+ Event types, signatures, retries, and payload details.
-
-
- API key and Client ID setup.
+
+ Full request/response schema and errors.