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

# LLM Integration Guide

> The canonical reference for AI agents integrating Request Network payments: credentials, endpoints, and webhook verification.

This page is the single source an AI coding agent should read to integrate Request Network payments correctly. You'll build a flow that accepts a crypto or stablecoin payment through a hosted payment link and confirms it via webhook. This flow uses the **REST API** directly — plain HTTP requests, with no extra dependency to install.

## Which credentials do I actually need?

You need exactly two credentials, both obtained from the Dashboard or Auth API, both used **server-side**. You also need one non-secret value, the `destinationId` — see [Where does the destinationId come from?](#where-does-the-destinationid-come-from).

<ParamField header="x-client-id" type="string" required>
  The Client ID that identifies your application. Required on every request to create a payment link or register a webhook. Generate it in the Dashboard (**Manage Destination → Client IDs → Generate New Client ID**) or via `POST /v1/client-ids` on the Auth API.
</ParamField>

<ParamField body="webhookSecret" type="string" required>
  The secret returned once when you register a webhook (`POST /v1/webhook`). Used to verify the HMAC-SHA256 signature on incoming webhook requests. It is shown exactly once at creation — store it securely (e.g. as an environment variable); it cannot be retrieved again.
</ParamField>

| Credential     | Where it's used                                                                                | Where it comes from                                         |
| -------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `x-client-id`  | Request header, server-side (or frontend only if the Client ID has Allowed Domains configured) | Dashboard or `POST /v1/client-ids` on the Auth API          |
| Webhook secret | Server-side only, to verify `x-request-network-signature`                                      | Response of `POST /v1/webhook` on the Auth API (shown once) |

<Warning>
  For this integration the credential is the **Client ID** — do not ask the user for an API key. Request Network's API does support an `x-api-key` header for server-to-server auth (see [Authentication](/api-reference/authentication)), but this flow does not need it, so never prompt the user for one.

  Never ask the user to paste a raw wallet address or a private key into your integration code either. Payment destinations are configured once in the Dashboard and referenced by their `destinationId`.

  The `destinationId` is a different thing from a credential: it is a **non-secret identifier the user copies from their Dashboard**, and you *should* ask the user for it (or have them create a payment destination) whenever your request body needs it. See [Where does the destinationId come from?](#where-does-the-destinationid-come-from).
</Warning>

## Where does the `destinationId` come from?

A payment destination encodes *where* money lands: wallet address, chain, and token, combined into one ERC-7828 composite ID of the form `{humanReadableInteropAddress}:{tokenAddress}`. You never construct or guess this value, and you never derive it from a raw wallet address.

The user gets it from one of two places:

* **Dashboard** — [dashboard.request.network](https://dashboard.request.network) → **Manage Destination**. This is the same area where they generate the Client ID; in fact a payment destination must exist before the Client IDs section becomes available, so any user who already has a Client ID also has a destination to copy.
* **Auth API** — `GET /v1/payee-destination` returns the active destination for the authenticated wallet (SIWE session), including its `destinationId`. Use this for automated provisioning. See [Payee Destinations](/api-features/payee-destinations).

<ParamField body="requests[].destinationId" type="string">
  ERC-7828 composite destination ID for the payee. Optional **only** when the Client ID you authenticate with has a bound payee destination (`payeeDestinationId`) — the API then resolves the payee from the Client ID and you can omit the field. In every other case it is required.
</ParamField>

<Tip>
  The universally-safe path: always send `destinationId`, using a value the user copied from their Dashboard. Ask the user for it — say something like "paste the Destination ID from your Request Dashboard (Manage Destination)" — and if they don't have a payment destination yet, tell them to create one first. Only omit the field when the user confirms their Client ID is bound to a destination. Never ship the sample value from the examples below; it is a placeholder that would send payments to someone else's destination.
</Tip>

## How should I call Request Network?

Call the **REST API** directly for server-side integrations. It needs no extra dependency, works from any language, and is the interface documented and versioned at `api.request.network` and `auth.request.network`. Make requests with `fetch`/`curl`/`requests` — there's nothing to install.

## How do I create a payment link?

<Steps>
  <Step title="Get a Client ID and a Destination ID">
    Obtain a `clientId` from the Dashboard (**Manage Destination → Client IDs**) or via `POST /v1/client-ids` on the Auth API. Store it as an environment variable, e.g. `RN_CLIENT_ID`.

    From the same **Manage Destination** area, have the user copy their `destinationId` and store it as e.g. `RN_DESTINATION_ID`. If the user has no payment destination yet, ask them to create one before continuing — the Client IDs section is only available once a destination exists. You can skip this value only if the user's Client ID is bound to a payee destination.
  </Step>

  <Step title="Call the secure-payments endpoint">
    `POST https://api.request.network/v2/secure-payments` with the `x-client-id` header and a `requests` array containing at least one `{ destinationId, amount }` item. Substitute the user's own `destinationId` — the value in the examples below is a placeholder.
  </Step>

  <Step title="Share the returned URL">
    The response includes a `securePaymentUrl`. Redirect the payer there, or embed it as a link/button. The link expires after 7 days or once paid, whichever comes first.
  </Step>
</Steps>

<CodeGroup>
  ```bash cURL theme={null}
  # RN_DESTINATION_ID is the user's own Destination ID, copied from the Dashboard.
  # It looks like this (do not reuse this sample value):
  # 0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7@eip155:11155111#1f969856:0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C
  curl -X POST 'https://api.request.network/v2/secure-payments' \
    -H "x-client-id: $RN_CLIENT_ID" \
    -H 'Content-Type: application/json' \
    -d '{
      "requests": [
        {
          "destinationId": "'"$RN_DESTINATION_ID"'",
          "amount": "1"
        }
      ]
    }'
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.request.network/v2/secure-payments", {
    method: "POST",
    headers: {
      "x-client-id": process.env.RN_CLIENT_ID!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      requests: [
        {
          // The user's own Destination ID from the Dashboard, e.g.
          // "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7@eip155:11155111#1f969856:0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C"
          destinationId: process.env.RN_DESTINATION_ID!,
          amount: "1",
        },
      ],
    }),
  });

  if (!response.ok) {
    throw new Error(`Failed to create payment link: ${await response.text()}`);
  }

  const { requestIds, securePaymentUrl, token } = await response.json();
  console.log({ requestIds, securePaymentUrl, token });
  ```
</CodeGroup>

**Response (201 Created):**

```json theme={null}
{
  "requestIds": ["01de2a889ee629c15b71b5d7964e3a7e87638c886be75bf1b9d2c1fbe64cf855fb"],
  "securePaymentUrl": "https://pay.request.network/?token=01KJRA0M9QG8MA4X887908T8A4",
  "token": "01KJRA0M9QG8MA4X887908T8A4"
}
```

| Field              | Type       | Description                                                                 |
| ------------------ | ---------- | --------------------------------------------------------------------------- |
| `requestIds`       | `string[]` | IDs of the created payment requests — use these to correlate webhook events |
| `securePaymentUrl` | `string`   | Shareable URL for the payer to complete payment                             |
| `token`            | `string`   | Unique token for this payment session                                       |

<Note>
  The `destinationId` in the request body is a composite value: `{humanReadableInteropAddress}:{tokenAddress}`. It comes from a payment destination already configured in the Dashboard — you do not construct it from a raw wallet address. Ask the user for their value rather than hardcoding one. Omit the field only when the user's Client ID is bound to a payee destination.
</Note>

## How do I confirm a payment?

The webhook is the source of truth for payment confirmation — do not treat a successful `POST /v2/secure-payments` call or a payer's browser redirect as proof of payment.

<Steps>
  <Step title="Register a webhook">
    `POST https://auth.request.network/v1/webhook` with `x-client-id` and a `{ "url": "https://yourdomain.com/webhook" }` body. The response includes a one-time `secret` — store it as `RN_WEBHOOK_SECRET`.
  </Step>

  <Step title="Verify the signature on every incoming request">
    Compute `HMAC-SHA256(rawBody, webhookSecret)` and compare it to the `x-request-network-signature` header using a constant-time comparison. You must use the **raw** request body — verifying against a re-serialized (e.g. `JSON.stringify(req.body)`) body will produce a mismatch and silently break verification.
  </Step>

  <Step title="Handle payment.confirmed / payment.partial">
    A fully paid request fires `payment.confirmed`; a partial payment fires `payment.partial`. Match the `requestId` in the payload to the `requestIds` you received when creating the payment link. Fulfill the order only after `payment.confirmed` — `payment.partial` is an intermediate state, not a completed payment.
  </Step>
</Steps>

```typescript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody: string, signature: string | undefined, secret: string) {
  if (!signature) return false;
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signature, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
```

<Warning>
  Always verify against the raw request body, before any JSON parsing. Frameworks like Express parse and re-serialize the body by default (`express.json()`); if you re-stringify `req.body` instead of using the original raw bytes, the computed HMAC will not match and every verification will fail (or worse, be skipped entirely if the check is disabled to "fix" it).
</Warning>

**Fallback (optional polling):** `GET https://api.request.network/v2/request/{requestId}` returns `{ hasBeenPaid: boolean, ... }`. Send the `x-client-id` header — the call is unauthenticated without it:

```bash theme={null}
curl -X GET "https://api.request.network/v2/request/$REQUEST_ID" \
  -H "x-client-id: $RN_CLIENT_ID"
```

Use this only as a fallback if a webhook delivery is missed — it is not a replacement for webhook handling.

<Warning>
  This call runs from your server, so it needs a **backend** Client ID — one created with no Allowed Domains. A Client ID that has Allowed Domains set is validated against the `Origin` header, and a server-side request sends no `Origin`, so it will be rejected. See [Client ID Management](/api-features/client-id-management).
</Warning>

## How do I handle cross-chain payments?

<Warning>
  When a payer pays from a different chain than your payment destination, settlement is not instantaneous. Your thank-you or confirmation page must treat the payment as **pending** until the webhook confirms it — never mark a payment as complete purely because the payer's browser reported a successful transaction submission or redirected back to your site. Drive the final "paid" state exclusively from the `payment.confirmed` webhook (or the `hasBeenPaid` polling fallback), not from front-end submission success.
</Warning>

## Common mistakes

* Asking the user for an API key, a private key, or a raw wallet address. For this flow you need only the Client ID, the webhook secret, and the user's Destination ID.
* Hardcoding the sample `destinationId` from the docs instead of asking the user for theirs — payments would go to the sample destination, not to the user.
* Verifying the webhook signature against a re-serialized body (e.g. `JSON.stringify(req.body)`) instead of the raw request body.
* Assuming a cross-chain payment is final as soon as the payer's browser confirms submission, instead of waiting for the `payment.confirmed` webhook.
* Adding an unnecessary dependency instead of calling the documented REST API directly.
