Checkout Links

View as Markdown

A 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#


POST /checkout-links

Request body#

FieldTypeRequiredNotes
namestringYes*Shown on the hosted page (e.g. "Order #1234"). *Or provide description. One of the two must yield a non-empty name.
amountintegeroneCents. For FIXED you almost always set this (minimum 50 in practice). Omit for shopper-driven DONATION.
currencystringNoISO 4217, e.g. "usd". Defaults to usd.
link_typestringNo"FIXED" (default), "DONATION", or "SUBSCRIPTION".
planstringSUBSCRIPTIONplan_… id. Required when link_type is SUBSCRIPTION (400 parameter_missing without it). Echoed on the 201 create body and on GET, persist either.
descriptionstringNoOptional 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, not type. Values are FIXED | DONATION | SUBSCRIPTION. Not CUSTOM / PRODUCT. Unknown values fall back to FIXED.

Example: a fixed one-off amount#

bash
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#

json
{
  "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_typeUse it for
FIXEDA one-off charge for a specific amount you set at creation. Default.
DONATIONA donation-style page where the shopper can enter their own amount.
SUBSCRIPTIONA recurring / subscription-oriented link.

For a catalogue product’s own share URL, see Products. Product binding is not a field on this create body.


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.

QueryNotes
limitDefault 10, max 100.
starting_afterThe previous page's next_cursor.
activeOptional filter. Absent lists both active and inactive links.
link_typeOptional filter: FIXED | DONATION | SUBSCRIPTION.
json
{
  "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, the client_secret does 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.