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

# Payment Links

> Create shareable checkout URLs with hosted pages. No frontend required.

# Payment Links

Payment links are shareable URLs that open a hosted checkout page. Create a link, share it via email, social media, or embed it in a button -- and customers can pay without you building any checkout UI.

## Create a Payment Link

```
POST /api/v1/payment-links
```

<ParamField body="amount" type="number" required>
  Payment amount.
</ParamField>

<ParamField body="currency" type="string" default="USD">
  Currency code: `USD`, `EUR`, `GBP`.
</ParamField>

<ParamField body="token" type="string" default="USDC">
  Token to accept: `USDC`, `USDT`, `SOL`.
</ParamField>

<ParamField body="description" type="string">
  Description shown on the checkout page.
</ParamField>

<ParamField body="max_uses" type="integer">
  Maximum number of times this link can be used. Omit for unlimited.
</ParamField>

<ParamField body="expires_at" type="string">
  ISO 8601 expiration date for the link itself.
</ParamField>

<ParamField body="metadata" type="object">
  Arbitrary key-value pairs.
</ParamField>

<ParamField body="onramp" type="boolean" default="false">
  Enable fiat onramp (NGN to USDC) for this link. The checkout page will present a **Pay with Bank** option powered by the PAJ Ramp flow.
</ParamField>

<ParamField body="amount_ngn" type="number">
  Original NGN amount for exact PAJ conversion. Bypasses a live FX re-quote at checkout time. Only relevant when `onramp` is `true`.
</ParamField>

<ParamField body="payer_service_charge" type="boolean" default="false">
  When `true` (and `onramp` is `true`), a transparent service charge — `max(₦30, ceil(amount_ngn × 2.5%))` — is added on top and billed to the payer rather than absorbed by the merchant.
</ParamField>

<ParamField body="collect_customer_info" type="boolean" default="false">
  When `true`, the checkout shows an expanded form to collect customer details (name, phone, company, billing address) before the payment step.
</ParamField>

<ParamField body="customer" type="CustomerObject">
  Optional pre-filled customer object. When present:

  * The checkout page **skips** the email / customer-info collection step entirely and shows a single **"Continue to Pay"** button instead.
  * The customer data is passed directly into the payment / onramp flow (no manual input required from the payer).
  * `max_uses` is **automatically forced to `1`** — customer-scoped links are always single-use, regardless of any `max_uses` value supplied in the request.

  | Field                   | Type                    | Description                                 |
  | ----------------------- | ----------------------- | ------------------------------------------- |
  | `email`                 | `string` **(required)** | Customer email address                      |
  | `name`                  | `string`                | Full name                                   |
  | `phone`                 | `string`                | Phone number (e.g. `+2348012345678`)        |
  | `company`               | `string`                | Company / organisation name                 |
  | `billing_address_line1` | `string`                | Address line 1                              |
  | `billing_address_line2` | `string`                | Address line 2                              |
  | `billing_city`          | `string`                | City                                        |
  | `billing_state`         | `string`                | State / province                            |
  | `billing_postal_code`   | `string`                | Postal / ZIP code                           |
  | `billing_country`       | `string`                | ISO 3166-1 alpha-2 country code (e.g. `NG`) |
</ParamField>

### Examples

<CodeGroup>
  ```bash cURL — standard link theme={null}
  curl -X POST https://api.zendfi.tech/api/v1/payment-links \
    -H "Authorization: Bearer zfi_test_your_key" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 25.00,
      "description": "Workshop Registration",
      "max_uses": 50,
      "metadata": {"event": "solana-workshop-march"}
    }'
  ```

  ```bash cURL — customer-scoped link theme={null}
  curl -X POST https://api.zendfi.tech/api/v1/payment-links \
    -H "Authorization: Bearer zfi_live_your_key" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 49.00,
      "description": "Order #1042 – Wireless Headphones",
      "onramp": true,
      "payer_service_charge": true,
      "customer": {
        "email": "ada@example.com",
        "name": "Ada Lovelace",
        "phone": "+2348012345678",
        "billing_city": "Lagos",
        "billing_country": "NG"
      }
    }'
  ```

  ```typescript SDK — standard link theme={null}
  const link = await zendfi.createPaymentLink({
    amount: 25.00,
    description: 'Workshop Registration',
    max_uses: 50,
    metadata: { event: 'solana-workshop-march' },
  });

  console.log(link.url); // https://checkout.zendfi.tech/checkout/abc123xyz
  ```

  ```typescript SDK — customer-scoped link theme={null}
  const link = await zendfi.createPaymentLink({
    amount: 49.00,
    description: 'Order #1042 – Wireless Headphones',
    onramp: true,
    payer_service_charge: true,
    // max_uses is automatically forced to 1
    customer: {
      email: 'ada@example.com',
      name: 'Ada Lovelace',
      phone: '+2348012345678',
      billing_city: 'Lagos',
      billing_country: 'NG',
    },
  });

  console.log(link.max_uses);      // 1  (forced automatically)
  console.log(link.customer_data); // { email: 'ada@example.com', … }
  console.log(link.url);
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "link_code": "abc123xyz",
  "description": "Workshop Registration",
  "amount": 25.00,
  "currency": "USD",
  "token": "USDC",
  "hosted_page_url": "https://checkout.zendfi.tech/checkout/abc123xyz",
  "payment_url": "https://checkout.zendfi.tech/checkout/abc123xyz",
  "max_uses": 50,
  "uses_count": 0,
  "expires_at": null,
  "is_active": true,
  "onramp": false,
  "payer_service_charge": false,
  "collect_customer_info": false,
  "customer_data": null,
  "created_at": "2026-03-01T12:00:00Z"
}
```

#### Response — customer-scoped link

```json theme={null}
{
  "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "link_code": "xyz789abc",
  "description": "Order #1042 – Wireless Headphones",
  "amount": 49.00,
  "currency": "USD",
  "token": "USDC",
  "hosted_page_url": "https://checkout.zendfi.tech/checkout/xyz789abc",
  "payment_url": "https://checkout.zendfi.tech/checkout/xyz789abc",
  "max_uses": 1,
  "uses_count": 0,
  "expires_at": null,
  "is_active": true,
  "onramp": true,
  "payer_service_charge": true,
  "collect_customer_info": false,
  "customer_data": {
    "email": "ada@example.com",
    "name": "Ada Lovelace",
    "phone": "+2348012345678",
    "billing_city": "Lagos",
    "billing_country": "NG"
  },
  "created_at": "2026-03-01T12:00:00Z"
}
```

***

## List Payment Links

```
GET /api/v1/payment-links
```

Returns all payment links for the authenticated merchant.

```typescript theme={null}
const links = await zendfi.listPaymentLinks();
```

***

## Get a Payment Link

```
GET /api/v1/payment-links/{link_code}
```

Retrieves a specific payment link by its code. This endpoint is **public** -- it does not require authentication. This is how the checkout page loads link data.

<ParamField path="link_code" type="string" required>
  The unique link code (e.g., `abc123xyz`).
</ParamField>

```typescript theme={null}
const link = await zendfi.getPaymentLink('abc123xyz');
```

***

## Create a Payment from a Link

```
POST /api/v1/payment-links/{link_code}/pay
```

Creates a new payment from a payment link. This is called by the checkout page when a customer opens the link. It is **public** -- no authentication required.

When this endpoint is called, it:

1. Validates the link is active, not expired, and has remaining uses.
2. Creates a new payment with the link's parameters.
3. Returns the payment details including wallet address and QR code.

***

## Use Cases

<CardGroup cols={2}>
  <Card title="Invoicing" icon="file-invoice">
    Create a link and email it to your client. They click, pay, done.
  </Card>

  <Card title="Social Commerce" icon="share-nodes">
    Share payment links on Twitter, Discord, or Telegram. Accept payments from anywhere.
  </Card>

  <Card title="Event Registration" icon="ticket">
    Set `max_uses` to your event capacity. Each registration creates a tracked payment.
  </Card>

  <Card title="Donations / Tips" icon="hand-holding-heart">
    Create an open-ended link for recurring or variable-amount contributions.
  </Card>
</CardGroup>

## Payment Links with Fiat Onramp

When `onramp` is set to `true`, the checkout page shows a **Pay with Bank** option. The customer transfers NGN via bank transfer, which is converted to USDC and settled directly to your wallet.

```typescript theme={null}
const link = await zendfi.createPaymentLink({
  amount: 25.00,
  description: 'Product Purchase',
  onramp: true,
  payer_service_charge: true, // transparent fee added on top, billed to payer
});
```

See the [Fiat Onramp guide](/guides/onramp) for details on the full flow.

***

## Customer-Scoped Links (Single-Use)

Pass a `customer` object when you already know who is paying — for example, when generating a link programmatically from an order management system or a checkout flow on your backend.

```typescript theme={null}
const link = await zendfi.createPaymentLink({
  amount: 120.00,
  description: 'Subscription renewal — April 2026',
  onramp: true,
  customer: {
    email: 'customer@example.com',
    name: 'Emeka Obi',
    phone: '+2349023456789',
    billing_country: 'NG',
  },
});
// link.max_uses === 1  (enforced automatically)
```

**Checkout behaviour when `customer` is present:**

1. The hosted checkout page loads normally.
2. Instead of showing the email / customer-info form it shows the customer's name and a **"Continue to Pay"** button.
3. Clicking the button proceeds directly to the payment step (crypto wallet or PAJ onramp virtual account screen), with the customer details pre-populated.

***

## Trust and Safety on Hosted Checkout

Hosted checkout includes security controls designed to protect both merchants and customers:

1. **Risk-based payment checks** are applied during payment creation, transaction build, and submission.
2. **Sanctions screening** is applied during create-payment and transaction-build checkpoints.
3. **Submission-time fraud gates** prevent stale-risk bypasses after initial checkout steps.

### What customers may see

Depending on risk outcomes, customers may experience:

* normal payment flow
* temporary hold / additional review requirement
* blocked payment attempt

### Recommended merchant UX copy

If a payment is blocked or held for review, show neutral language:

> "Your payment requires additional security review. Please contact support with your payment ID."

Avoid showing internal fraud rule details or threshold values in customer-facing UI.

For full policy and operations guidance, see [Fraud and Compliance](/security/fraud-and-compliance).

<Note>
  Because `max_uses` is locked to `1`, customer-scoped links are ideal for **invoice payment links**, **programmatic order links**, and any scenario where you need a one-time, identity-bound checkout URL.
</Note>
