Checkout Links
View as MarkdownA checkout link is a WorthyPayments-hosted payment page. Create one server-side, redirect the shopper to its URL, and the hosted page collects the card and runs the full engine (fraud screening, routing, 3-D Secure) with no card data ever touching your code. This is the redirect integration, and the lowest-PCI way to take a payment.
POST https://api.worthypayments.com/v1/checkout-links
GET https://api.worthypayments.com/v1/checkout-links
GET https://api.worthypayments.com/v1/checkout-links/{id}
Scopes: checkout_links:create to make one, checkout_links:read to list or fetch them
back. Both are server-only. A publishable key (pk_) cannot reach either; use a
secret key (sk_). See Authentication.
There is no update or delete endpoint. See Changing the price.
The redirect flow#
Create a checkout link#
POST /checkout-links
Request body#
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes* | Shown on the hosted page (e.g. "Order #1234"). *Or provide description. One of the two must yield a non-empty name. |
amount | integer | one | Cents. For FIXED you almost always set this (minimum 50 in practice). Omit for shopper-driven DONATION. |
currency | string | No | ISO 4217, e.g. "usd". Defaults to usd. |
link_type | string | No | "FIXED" (default), "DONATION", or "SUBSCRIPTION". |
plan | string | SUBSCRIPTION | plan_… id. Required when link_type is SUBSCRIPTION (400 parameter_missing without it). Echoed on the 201 create body and on GET, persist either. |
description | string | No | Optional blurb; also used as the name fallback when name is empty. |
There is no success_url / cancel_url on this create body today. The hosted page owns
the post-pay experience; reconcile via webhooks.
The field is
link_type, nottype. Values areFIXED|DONATION|SUBSCRIPTION. NotCUSTOM/PRODUCT. Unknown values fall back toFIXED.
Example: a fixed one-off amount#
curl https://api.worthypayments.com/v1/checkout-links \
-H "x-api-key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Order #1234",
"amount": 4999,
"currency": "usd",
"link_type": "FIXED"
}'
Response#
{
"id": "9f2c1b84-77ad-4e0c-9c31-0b6e5d2a4413",
"object": "checkout_link",
"url": "https://app.worthypayments.com/checkout/abc123",
"code": "abc123",
"active": true
}
Redirect the shopper to url. It is absolute and points at our hosted checkout, so it is
safe to hand straight to a redirect. code is the short slug at the end of it, use that
if you need to build the link yourself.
Link types#
link_type | Use it for |
|---|---|
FIXED | A one-off charge for a specific amount you set at creation. Default. |
DONATION | A donation-style page where the shopper can enter their own amount. |
SUBSCRIPTION | A recurring / subscription-oriented link. |
For a catalogue product’s own share URL, see Products. Product binding is not a field on this create body.
Read your links back#
GET https://api.worthypayments.com/v1/checkout-links
GET https://api.worthypayments.com/v1/checkout-links/{id}
Scope: checkout_links:read. A separate grant from checkout_links:create. Tick it
on the key in Settings → Developers → API keys; a key that only creates links gets
403 permission_denied here.
The list is cursor-paged the same way as GET /charges, and
next_cursor is omitted rather than null when there is no next page.
| Query | Notes |
|---|---|
limit | Default 10, max 100. |
starting_after | The previous page's next_cursor. |
active | Optional filter. Absent lists both active and inactive links. |
link_type | Optional filter: FIXED | DONATION | SUBSCRIPTION. |
{
"object": "list",
"data": [
{
"object": "checkout_link",
"id": "df62a7a3-e2de-4f47-8635-e3c9adfebddf",
"url": "https://app.worthypayments.com/checkout/lKHx64iW",
"code": "lKHx64iW",
"name": "Order #1234",
"description": "",
"link_type": "FIXED",
"currency": "USD",
"amount": 4999,
"min_amount": null,
"max_amount": null,
"max_uses": null,
"times_used": 0,
"active": true,
"product_id": null,
"store_id": null,
"expires_at": null,
"created": 1788100472,
"updated": 1788100472
}
],
"has_more": false
}
link_type reads back as the same value you sent, FIXED, not the internal
PAYMENT_LINK_TYPE_FIXED. Timestamps are Unix seconds. amount is minor units, and is
null on a DONATION link with no fixed amount.
Changing the price#
A link's amount is fixed once created. There is no update endpoint, PATCH and
PUT on /checkout-links/{id} are not served and answer 404. Two ways forward:
- Create a new link and redirect to that one instead. Old links keep working, so deactivate the previous one in the dashboard if it must stop taking money.
- Edit it in the dashboard (Payment Links → the link → Edit), which can change
the amount in place and keeps the same
code, so a URL you have already sent out stays valid at the new price.
Re-pricing a cart mid-checkout is a different problem, and it has a real answer. If the shopper is on your page rather than ours, use the Payment Element and
PATCH /v1/payment_intents/{id}: it rewrites the amount on an intent that is already mounted, theclient_secretdoes not change, and nothing has to be re-rendered. Checkout links have no equivalent because the shopper is on our page by then, and the page has already been handed a price.
After payment#
The hosted page runs the entire payment lifecycle, including any 3-D Secure step-up. You
do not handle requires_action yourself in the redirect flow. Reconcile the order on
your side via webhooks or by reading the charge once you have its id. See
Webhooks.
Prefer to keep the buyer on your domain instead of redirecting? Use the Payment Element. For a pure server-to-server charge after your own tokenization, see
POST /charges.
