---
title: Payments | API Docs
description: Request payments over iMessage and collect with Apple Pay — settling to your own Stripe account by default, or through the Natural custodial wallet.
---

Request a payment from a recipient over iMessage. You create a payment request, send its `checkout_url` to the recipient, and they pay with Apple Pay or card. Funds settle **directly to your own Stripe account** — Linq never holds the money.

### How it works

1. **Create** a payment request with an amount and currency. You get back a `checkout_url` and a `status` of `requested`.
2. **Send** the `checkout_url` to the recipient as a `link` message part so it arrives as a tappable card (see *Sending the link* below).
3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a supported iPhone, web checkout everywhere else).
4. You receive a **`payment.succeeded`** webhook and the request’s `status` becomes `succeeded`. Requests you don’t collect eventually `expire`.

### Connected accounts (Stripe Standard, direct charges)

Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq orchestrates the request and the checkout but is never in the funds flow.

**Refunds, disputes, and chargebacks are handled by you, in your own Stripe Dashboard.** Because charges settle directly to your account, Linq has no custody of the funds and cannot issue refunds or contest disputes on your behalf — and there is no refund/dispute endpoint in this API by design. Use the Stripe Dashboard (or the Stripe API on your own account) for the money lifecycle after a payment succeeds.

### Getting set up

Open **Agent Pay** in your Linq dashboard (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**, and complete Stripe’s onboarding (business details + a bank account). When your account reaches `charges_enabled`, request creation unlocks; until you connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep collecting even while Stripe finishes background verification.

### Subscriptions

Set `mode: subscription` on `POST /v3/payment_requests` to start an **auto-renewing subscription** instead of a one-time charge. Instead of an amount, you pass a `price_id` — an active **recurring Price** on your connected Stripe account (create one in your Stripe Dashboard under Product catalog; if you sell through Stripe Payment Links today, reuse the price your link is built from). The recipient pays the first invoice at the same checkout, and their payment method is saved to the subscription for automatic renewals.

The division of labor is deliberate: **Linq handles the first payment, your Stripe account handles the rest.** The request reaches `succeeded` when the first invoice is paid; from then on the subscription lives entirely on your connected account. The response’s `stripe` object gives you the join keys — `customer_id` and `subscription_id` — so renewals, plan changes, dunning, and cancellation are managed with your own Stripe Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on the Customer and Subscription, so correlating in either direction is trivial. There are no renewal webhooks from Linq by design.

#### Free trials

Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the subscription with a free trial. The checkout still collects the recipient’s payment method — the pay sheet shows “$0 due today” with the first charge date — and saves it to the subscription; Stripe bills it automatically when the trial ends. The request reaches `succeeded` when the card is collected, and the response carries `trial_end`. If the trial would end without a payment method on file, the subscription cancels rather than generating unpayable invoices. Trial lifecycle after checkout (extending, ending early) is managed in your own Stripe account via `stripe.subscription_id`.

A subscription request you cancel (or that expires unpaid) cancels the incomplete Stripe subscription — nothing lingers on your account.

### Pre-created customers

By default each request stands alone: payment mode attaches no Customer, and subscription mode creates a fresh one. If you already manage Customers on your connected account, pass their id as `customer_id` (`cus_...`) on create — in payment mode the charge lands on that customer’s payment history, and in subscription mode the subscription is created on them instead of on a new Customer. The id must reference an existing, non-deleted customer on your connected account or the request fails with `400`. We never modify a customer you pass — no metadata is stamped on it.

### Sending the link

Deliver the `checkout_url` as a **`link` message part** via `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your branding (title, amount, image) instead of a bare URL, which converts far better. A `link` part must be the only part in the message. See [Rich Link Previews](/guides/messaging/sending-messages/index.md).

On a supported iPhone the link opens an **Apple Pay App Clip** — a native, no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to \~24 hours to activate on Apple’s side, during which links open the web checkout.

### Sending it as a card instead

A `link` part is one way to deliver a request. The other is the **`agentpay` experience**, which sends the same request as a native card in Linq’s iMessage app — the amount and reason are drawn in the bubble, and it turns itself into “Paid” in place once the payment succeeds, without a second message.

Send it to `POST /v3/chats/{chatId}/messages`:

```
{
  "message": {
    "experience": {
      "name": "agentpay",
      "action": "request_payment",
      "params": { "checkout_url": "https://zero.linqapp.com/pay/acme?session=tok_..." }
    }
  }
}
```

`checkout_url` is the only required field — pass back exactly what `POST /v3/payment_requests` returned. **The amount and reason are read from that request, never from you**, so the card can never claim a different figure than the checkout will charge. Optional `title` and `note` override the copy only. The link must be one of your own payment requests; another partner’s is rejected.

The trade-off against a `link` part: a card is an app card, so it is iMessage-only, and recipients without the app see a static version of it. A link works everywhere and is what opens the Apple Pay App Clip. Send whichever suits the conversation — both settle the same payment request and fire the same webhooks.

### Webhooks

Subscribe to payment lifecycle events to reconcile server-side rather than polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`. Each event carries the payment request id, amount, currency, and your `metadata`. See [Webhooks](/guides/webhooks/index.md).

## The flow

Collecting a payment is three steps and one webhook:

1. **Create a payment request** with an amount and currency. You get back a `checkout_url` and a `status` of `requested`.
2. **Send the `checkout_url`** to the recipient as a `link` message part, so it arrives as a tappable card rather than a bare URL — see [Sending payment links](/guides/payments/sending-payment-links/index.md).
3. The recipient **pays** on the hosted checkout — an Apple Pay App Clip on a supported iPhone, web checkout everywhere else.
4. You receive a **`payment.succeeded`** [webhook](/guides/payments/webhooks/index.md) and the request’s `status` becomes `succeeded`.

## Create a payment request

- [cURL](#tab-panel-186)
- [TypeScript](#tab-panel-187)
- [Python](#tab-panel-188)
- [Go](#tab-panel-189)

Terminal window

```
curl -X POST https://api.linqapp.com/api/partner/v3/payment_requests \
  -H "Authorization: Bearer $LINQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "amount": 497,
      "currency": "usd",
      "description": "Coffee with Ava",
      "metadata": {
        "order_id": "order_8675309"
      }
    }'
```

```
await client.paymentRequests.create({
  amount: 497,
  currency: "usd",
  description: "Coffee with Ava",
  metadata: {
    order_id: "order_8675309",
  },
});
```

```
client.paymentRequests.create(
    amount=497,
    currency="usd",
    description="Coffee with Ava",
    metadata={
        "order_id": "order_8675309",
    },
)
```

```
client.PaymentRequests.Create(context.TODO(), linq.PaymentRequestNewParams{
  Amount: linq.F(497),
  Currency: linq.F("usd"),
  Description: linq.F("Coffee with Ava"),
  Metadata: linq.F(map[string]any{
    OrderId: linq.F("order_8675309"),
  }),
})
```

`amount` is in the currency’s minor units (cents for `usd`), with a `50`-cent minimum. The response includes the `checkout_url` you send to the recipient and an `expires_at` after which the request can no longer be paid.

Store your own identifiers in `metadata` (up to 49 keys) — e.g. an order id or the chat id — and they’re echoed back on retrieval and on every `payment.*` webhook, so you can reconcile against your records. Keys beginning with `linq_` are reserved. Metadata is also stamped on the Stripe objects created on your connected account, so the charge is correlatable from your Stripe Dashboard too.

## Payment rails

Every request settles on a **rail**, chosen with the `rail` parameter:

- **`stripe`** (default) — the direct-charge flow above. Funds settle directly to your own connected Stripe account and you are the merchant of record. See [Connected accounts](/guides/payments/connected-accounts/index.md).
- **`natural`** — collect through the **Natural** custodial wallet instead of Stripe. The payer is billed by phone number (`payer_handle`) and the request is sent from a line your organization owns (`from`). Available once your organization has completed Natural merchant onboarding; until then a `rail: natural` request returns `403` with a message pointing you to onboarding.

Omit `rail` and the request uses `stripe`. Subscriptions, free trials, and pre-created customers are Stripe-rail features — the rest of this guide describes the Stripe rail unless noted.

On the Natural rail, balances, payouts, and any wallet activity beyond the requests you create through Linq live in your Natural account. Use your **Natural dashboard** — or register your **own webhooks in Natural** — for wallet-level visibility.

## Subscriptions

Set `mode: subscription` with a recurring `price_id` from your connected Stripe account to start an **auto-renewing subscription** instead of a one-time charge:

- [cURL](#tab-panel-190)
- [TypeScript](#tab-panel-191)
- [Python](#tab-panel-192)
- [Go](#tab-panel-193)

Terminal window

```
curl -X POST https://api.linqapp.com/api/partner/v3/payment_requests \
  -H "Authorization: Bearer $LINQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "mode": "subscription",
      "price_id": "price_1QAbCdEfGhIjKlMn",
      "description": "Coaching membership",
      "metadata": {
        "member_id": "member_42"
      }
    }'
```

```
await client.paymentRequests.create({
  mode: "subscription",
  price_id: "price_1QAbCdEfGhIjKlMn",
  description: "Coaching membership",
  metadata: {
    member_id: "member_42",
  },
});
```

```
client.paymentRequests.create(
    mode="subscription",
    price_id="price_1QAbCdEfGhIjKlMn",
    description="Coaching membership",
    metadata={
        "member_id": "member_42",
    },
)
```

```
client.PaymentRequests.Create(context.TODO(), linq.PaymentRequestNewParams{
  Mode: linq.F("subscription"),
  PriceId: linq.F("price_1QAbCdEfGhIjKlMn"),
  Description: linq.F("Coaching membership"),
  Metadata: linq.F(map[string]any{
    MemberId: linq.F("member_42"),
  }),
})
```

Instead of an `amount`, you pass the id of an active **recurring Price** on your Stripe account (create one under Product catalog in your Stripe Dashboard; if you sell through Stripe Payment Links today, reuse the price your link is built from). The recipient pays the first invoice at the same checkout — the terms (“$9.99/month until canceled”) are disclosed on the pay sheet — and their payment method is saved to the subscription for automatic renewals.

**Linq handles the first payment; your Stripe account handles the rest.** The request reaches `succeeded` when the first invoice is paid. From then on the subscription lives entirely on your connected account: the response’s `stripe` object carries `customer_id` and `subscription_id`, so renewals, plan changes, dunning, and cancellation are managed with your own Stripe Dashboard/API and your own Stripe webhooks. Linq does not emit renewal events.

Canceling an unpaid subscription request (or letting it expire) cancels the incomplete Stripe subscription — nothing lingers on your account.

### Free trials

Add `trial_period_days` (1–730) — or a fixed future `trial_end` timestamp — to start the subscription with a free trial:

- [cURL](#tab-panel-194)
- [TypeScript](#tab-panel-195)
- [Python](#tab-panel-196)
- [Go](#tab-panel-197)

Terminal window

```
curl -X POST https://api.linqapp.com/api/partner/v3/payment_requests \
  -H "Authorization: Bearer $LINQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "mode": "subscription",
      "price_id": "price_1QAbCdEfGhIjKlMn",
      "trial_period_days": 14,
      "description": "Coaching membership",
      "metadata": {
        "member_id": "member_42"
      }
    }'
```

```
await client.paymentRequests.create({
  mode: "subscription",
  price_id: "price_1QAbCdEfGhIjKlMn",
  trial_period_days: 14,
  description: "Coaching membership",
  metadata: {
    member_id: "member_42",
  },
});
```

```
client.paymentRequests.create(
    mode="subscription",
    price_id="price_1QAbCdEfGhIjKlMn",
    trial_period_days=14,
    description="Coaching membership",
    metadata={
        "member_id": "member_42",
    },
)
```

```
client.PaymentRequests.Create(context.TODO(), linq.PaymentRequestNewParams{
  Mode: linq.F("subscription"),
  PriceId: linq.F("price_1QAbCdEfGhIjKlMn"),
  TrialPeriodDays: linq.F(14),
  Description: linq.F("Coaching membership"),
  Metadata: linq.F(map[string]any{
    MemberId: linq.F("member_42"),
  }),
})
```

The checkout still collects the recipient’s payment method (the pay sheet shows **$0 due today** with the first charge date) and saves it to the subscription; Stripe bills it automatically when the trial ends. The request reaches `succeeded` when the card is collected — no money moves until `trial_end`, which is echoed on the response and every `payment.*` webhook. If a trial somehow ends without a payment method on file, the subscription cancels rather than generating unpayable invoices. Post-checkout trial changes (extending, ending early) are managed in your own Stripe account via `stripe.subscription_id`.

## Pre-created customers

By default each request stands alone: payment mode attaches no Stripe Customer, and subscription mode creates a fresh one. If you already manage Customers on your connected account, pass their id as `customer_id` (`cus_...`) when creating the request:

- [cURL](#tab-panel-198)
- [TypeScript](#tab-panel-199)
- [Python](#tab-panel-200)
- [Go](#tab-panel-201)

Terminal window

```
curl -X POST https://api.linqapp.com/api/partner/v3/payment_requests \
  -H "Authorization: Bearer $LINQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "amount": 497,
      "currency": "usd",
      "customer_id": "cus_QAbCdEfGhIjKlMn",
      "description": "Coffee with Ava"
    }'
```

```
await client.paymentRequests.create({
  amount: 497,
  currency: "usd",
  customer_id: "cus_QAbCdEfGhIjKlMn",
  description: "Coffee with Ava",
});
```

```
client.paymentRequests.create(
    amount=497,
    currency="usd",
    customer_id="cus_QAbCdEfGhIjKlMn",
    description="Coffee with Ava",
)
```

```
client.PaymentRequests.Create(context.TODO(), linq.PaymentRequestNewParams{
  Amount: linq.F(497),
  Currency: linq.F("usd"),
  CustomerId: linq.F("cus_QAbCdEfGhIjKlMn"),
  Description: linq.F("Coffee with Ava"),
})
```

In payment mode the charge lands on that customer’s payment history; in subscription mode the subscription is created on them instead of on a new Customer. The id must reference an existing, non-deleted customer on your connected account or the request fails with `400`. A customer you pass is never modified — no metadata is stamped on it.

## Before you can charge

On the default `stripe` rail, payments settle **directly to your own Stripe account**, so you connect Stripe once before your first request. New accounts also need to clear Stripe’s verification. See [Connected accounts](/guides/payments/connected-accounts/index.md) for the one-time setup and what “merchant of record” means for you (including refunds and disputes). The `natural` rail instead requires your organization to complete Natural merchant onboarding.

## Reference

- `POST /v3/payment_requests` — [Create a payment request](/api/resources/payment_requests/methods/create/index.md)
- `GET /v3/payment_requests/{paymentRequestId}` — [Retrieve a payment request](/api/resources/payment_requests/methods/retrieve/index.md)
- `GET /v3/payment_requests` — [List payment requests](/api/resources/payment_requests/methods/list/index.md)
- `POST /v3/payment_requests/{paymentRequestId}/cancel` — [Cancel a payment request](/api/resources/payment_requests/methods/cancel/index.md)
