Skip to content
Get started

Payment Requests

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.

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.

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.

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.

Create a payment request
client.paymentRequests.create(PaymentRequestCreateParams { amount, currency, customer_id, 11 more } params, RequestOptionsoptions?): PaymentRequest { id, amount, checkout_url, 18 more }
POST/v3/payment_requests
Get a payment request
client.paymentRequests.retrieve(stringpaymentRequestID, RequestOptionsoptions?): PaymentRequest { id, amount, checkout_url, 18 more }
GET/v3/payment_requests/{paymentRequestId}
List payment requests
client.paymentRequests.list(PaymentRequestListParams { limit, offset, status } query?, RequestOptionsoptions?): PaymentRequestListResponse { data, has_more, object }
GET/v3/payment_requests
Cancel a payment request
client.paymentRequests.cancel(stringpaymentRequestID, RequestOptionsoptions?): PaymentRequest { id, amount, checkout_url, 18 more }
POST/v3/payment_requests/{paymentRequestId}/cancel
ModelsExpand Collapse
PaymentRequest { id, amount, checkout_url, 18 more }
id: string

Unique identifier of the payment request.

formatuuid
amount: number

Amount in the currency’s minor units. In subscription mode this is the recurring amount (price × quantity) the recipient pays per interval, starting at checkout.

formatint64
checkout_url: string

URL the recipient opens to pay: https://zero.linqapp.com/pay/{slug}?session=..., where {slug} is your partner checkout slug.

created_at: string
formatdate-time
currency: string
mode: "payment" | "subscription"

Whether this request collects a one-time charge or starts a subscription.

One of the following:
"payment"
"subscription"
object: string
status: "requested" | "succeeded" | "canceled" | "expired"

Lifecycle status of the payment request.

One of the following:
"requested"
"succeeded"
"canceled"
"expired"
description?: string
expires_at?: string

When an unpaid request auto-expires.

formatdate-time
interval?: "day" | "week" | "month" | "year"

Subscription mode — how often the subscription renews.

One of the following:
"day"
"week"
"month"
"year"
interval_count?: number

Subscription mode — intervals per renewal (e.g. 3 + month = quarterly).

formatint64
metadata?: Record<string, string>
natural?: Natural { payment_request_id, transaction_id }

Natural-rail join keys, present when rail: natural.

payment_request_id?: string

The Natural payment request (prq_...).

transaction_id?: string

The settled transaction (txn_...).

price_id?: string

Subscription mode — the recurring price this request subscribes to.

quantity?: number

Subscription mode — units of the price subscribed to.

formatint64
rail?: "stripe" | "natural"

The rail this request settled on.

One of the following:
"stripe"
"natural"
stripe?: Stripe { customer_id, payment_intent_id, subscription_id }

Ids of the Stripe objects created on your connected account — your join keys into your own Stripe Dashboard, webhooks, and API. After a subscription’s first payment succeeds, its ongoing lifecycle (renewals, plan changes, cancellation) is managed in your Stripe account using subscription_id.

customer_id?: string

The Customer this request is attached to (cus_...). Always set in subscription mode (created for you unless you passed customer_id); set in payment mode only when you passed one.

payment_intent_id?: string

The PaymentIntent collected at checkout (pi_...).

subscription_id?: string

Subscription mode — the Subscription (sub_...).

trial_end?: string

Subscription mode — when the free trial ends and the first charge happens. Present only on trial requests; paid_at/succeeded mean the payment method was collected (no funds move until this time).

formatdate-time
updated_at?: string
formatdate-time
PaymentRequestListResponse { data, has_more, object }
data: Array<PaymentRequest { id, amount, checkout_url, 18 more } >
id: string

Unique identifier of the payment request.

formatuuid
amount: number

Amount in the currency’s minor units. In subscription mode this is the recurring amount (price × quantity) the recipient pays per interval, starting at checkout.

formatint64
checkout_url: string

URL the recipient opens to pay: https://zero.linqapp.com/pay/{slug}?session=..., where {slug} is your partner checkout slug.

created_at: string
formatdate-time
currency: string
mode: "payment" | "subscription"

Whether this request collects a one-time charge or starts a subscription.

One of the following:
"payment"
"subscription"
object: string
status: "requested" | "succeeded" | "canceled" | "expired"

Lifecycle status of the payment request.

One of the following:
"requested"
"succeeded"
"canceled"
"expired"
description?: string
expires_at?: string

When an unpaid request auto-expires.

formatdate-time
interval?: "day" | "week" | "month" | "year"

Subscription mode — how often the subscription renews.

One of the following:
"day"
"week"
"month"
"year"
interval_count?: number

Subscription mode — intervals per renewal (e.g. 3 + month = quarterly).

formatint64
metadata?: Record<string, string>
natural?: Natural { payment_request_id, transaction_id }

Natural-rail join keys, present when rail: natural.

payment_request_id?: string

The Natural payment request (prq_...).

transaction_id?: string

The settled transaction (txn_...).

price_id?: string

Subscription mode — the recurring price this request subscribes to.

quantity?: number

Subscription mode — units of the price subscribed to.

formatint64
rail?: "stripe" | "natural"

The rail this request settled on.

One of the following:
"stripe"
"natural"
stripe?: Stripe { customer_id, payment_intent_id, subscription_id }

Ids of the Stripe objects created on your connected account — your join keys into your own Stripe Dashboard, webhooks, and API. After a subscription’s first payment succeeds, its ongoing lifecycle (renewals, plan changes, cancellation) is managed in your Stripe account using subscription_id.

customer_id?: string

The Customer this request is attached to (cus_...). Always set in subscription mode (created for you unless you passed customer_id); set in payment mode only when you passed one.

payment_intent_id?: string

The PaymentIntent collected at checkout (pi_...).

subscription_id?: string

Subscription mode — the Subscription (sub_...).

trial_end?: string

Subscription mode — when the free trial ends and the first charge happens. Present only on trial requests; paid_at/succeeded mean the payment method was collected (no funds move until this time).

formatdate-time
updated_at?: string
formatdate-time
has_more: boolean

Whether more results exist beyond this page.

object: "list"