High Wire Payments

Hosted checkout

Create a payment link from your server, hand it to the shopper, and confirm the payment before you fulfil the order, with no card data and no browser code on your side.

Use the hosted checkout when you want neither card data nor a payment page on your side. Your server sends the cart and receives a link; the shopper pays on a page High Wire serves. Both calls are server-to-server under your Basic credentials: no publishable key and no browser integration.

POST /v1/payments/checkout-session
POST /v1/payments/{id}/checkout-session/link

Availability. The hosted checkout must be enabled on your account. Where it is not, the create call answers 400 with code: "HW_UNSUPPORTED" and writes nothing.

How it works

  1. Your server creates the checkout and receives paymentId and checkoutUrl.
  2. You hand the link to the shopper: a redirect, an e-mail, a QR code, a chat message. It works on any device.
  3. The shopper opens the page and pays. The page shows the order and your account name; a declined card can be retried on the same page.
  4. Once the payment is captured the page sends the shopper to your successUrl. Confirm the payment on your server before you fulfil the order, see Return URLs.

Create a checkout

POST /v1/payments/checkout-session

Idempotency-Key: honoured. A replay answers the same body without checkoutUrl, see The link.

FieldTypeRequiredRules
lineItemsarrayyes1 to 100 items.
lineItems[].namestringyes1 to 200 characters. Shown to the shopper.
lineItems[].amountintegeryesUnit price in cents, at least 1.
lineItems[].quantityintegeryesAt least 1.
shippingAmountintegernoCents, at least 0. Default 0.
taxAmountintegernoCents, at least 0. Default 0.
discountAmountintegernoCents, at least 0, subtracted from the total. Default 0.
amountintegernoOptional cross-check in cents. The gateway computes the total itself; a value that differs answers 400.
currencystringyesusd, case-insensitive. The only currency served.
customerEmailstringnoA valid e-mail. The page shows it masked only. On most accounts the shopper may leave the page's e-mail field blank to use it; see What the shopper sees.
successUrlstringyesWhere the page sends the shopper once the payment is captured. An absolute https URL (http only on localhost, 127.0.0.1 or [::1]), at most 2048 characters.
cancelUrlstringyesWhere the page's "back to the store" link leads a shopper who leaves without paying. Same rules as successUrl.
savePaymentMethodForFutureUsebooleanyestrue shows the shopper an unticked save-card box with the card form; the card is saved only if they tick it and pay by card. false shows no box and saves nothing. No default. Fixed for the life of the link, see Saving the card.
expiresInMinutesintegernoHow long the link stays payable: 30 to 1440. Default 1440 (24 hours).
referenceIdstringnoYour own reference for the order, 1 to 128 characters.
metadataobjectnoAny JSON object of at most 4096 bytes. Stored with the payment exactly as sent.

An optional field sent as null is treated as absent. referenceId and metadata are stored but not returned by the API today, so correlate the checkout with your order by paymentId. lineItems[].name, referenceId and metadata are stored and shown back (item names to the shopper): never put card numbers, security codes or other sensitive data in them.

The total is computed on the server, in integer cents: the line items (amount × quantity) plus shippingAmount and taxAmount, minus discountAmount. It must be between 2 and 2147483647. Anything else answers a plain 400 that states the computed total.

curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/checkout-session \
  -u "YOUR_API_KEY:YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0193c2e9-4f9a-7c2f-9c83-1b2b3c4d5e6f" \
  -d '{
    "lineItems": [
      { "name": "Trail Runner (size 42)", "amount": 2500, "quantity": 2 },
      { "name": "Merino socks", "amount": 1500, "quantity": 1 }
    ],
    "shippingAmount": 500,
    "taxAmount": 600,
    "discountAmount": 1000,
    "amount": 6600,
    "currency": "usd",
    "customerEmail": "jordan@example.com",
    "successUrl": "https://merchant.example.com/order/complete",
    "cancelUrl": "https://merchant.example.com/cart",
    "savePaymentMethodForFutureUse": false,
    "expiresInMinutes": 1440,
    "referenceId": "order-1042",
    "metadata": { "cartId": "c_123" }
  }'

201 Created:

{
  "paymentId": "0192a0c4-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
  "status": "PENDING",
  "amount": 6600,
  "currency": "usd",
  "checkoutUrl": "https://checkout-staging.highwirepayments.com/0192a0c4-1b2c-7d3e-9f4a-5b6c7d8e9f0a#hw_hc_…",
  "expiresAt": "2026-10-20T14:05:00.000Z"
}
FieldTypeNotes
paymentIdstringPersist it: you confirm the payment, reissue the link and refund with it.
statusenumAlways PENDING at creation.
amountintegerThe total in cents, as computed above.
currencystringusd.
checkoutUrlstringThe link to hand the shopper. Returned once; absent on an idempotent replay.
expiresAtstringWhen the link stops being payable, ISO 8601.

The checkout host is checkout-staging.highwirepayments.com on staging and checkout.highwirepayments.com in production. Hand the URL over exactly as returned: do not parse it, rebuild it or shorten it.

StatusBodyWhen
201The response above.Created.
400Validation errorA field failed its rule, including a currency other than usd, and savePaymentMethodForFutureUse: true on an account whose hosted page cannot save cards (see Saving the card).
400Plain errorThe total disagrees with amount, or is out of range.
400HW_UNSUPPORTEDThe hosted checkout is not enabled on your account.
401Plain errorInvalid credentials or a disabled account.
409, 422Plain errorIdempotency key in flight or reused with a different body.
409HW_NO_ACTIVE_PROVIDER_ACCOUNTNo account of yours takes new payments. Not stored under the Idempotency-Key, so the same key can be retried once High Wire has resolved it.
  • It is shown once. A replay of the create call under the same Idempotency-Key returns the same body without checkoutUrl.
  • Send it to the shopper and nowhere else. Whoever has the link can pay the checkout, so keep it out of logs and analytics.
  • Reissue it on demand. If you lost the link, or it may have reached someone it should not have, ask for a new one. The previous link stops working at once; only a payment already in progress on the old page (a wallet confirmation, for example) can still complete.

Saving the card

With savePaymentMethodForFutureUse: true the page shows an unticked "Save this card for future purchases" box with the card form. The card is saved only if the shopper ticks it and pays by card.

  • Wallets never save. A payment with Apple Pay or Google Pay saves nothing, ticked or not.
  • Whose card. It is saved for the e-mail the payer used on the page.
  • What it allows. The bounds of every saved card: charged again only with the customer present, within 24 hours, at most twice, each time for at most the amount of the payment that first saved the card: this checkout's total, unless the card was already saved. Saving it again opens nothing new. It never allows charging the customer later without them.
  • The ids. No response returns customerId or paymentMethodId: not the create, not the page, not GET /v1/payments/{id}. Whoever holds the link could charge the card with them. The saved card is reported through the reconciliation channels set up for your account with High Wire at onboarding.
  • Fixed for the link. A reissued link keeps the setting; to change it, create a new checkout. The same Idempotency-Key sent with the other value answers 422.
  • Not every account can save. Where your account's hosted page cannot save cards, the create answers 400 and writes nothing. If your account's page changes after the link was created, the page shows no box.
{
  "statusCode": 400,
  "message": "Saving cards is not available on this account's hosted checkout",
  "fieldErrors": {
    "savePaymentMethodForFutureUse": [
      "Saving cards is not available on this account's hosted checkout"
    ]
  }
}
POST /v1/payments/{id}/checkout-session/link

Idempotency-Key: ignored. No request body. Every call issues a new link and revokes the previous one. The expiry does not move.

curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/0192a0c4-1b2c-7d3e-9f4a-5b6c7d8e9f0a/checkout-session/link \
  -u "YOUR_API_KEY:YOUR_SECRET"

201 Created:

{
  "paymentId": "0192a0c4-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
  "checkoutUrl": "https://checkout-staging.highwirepayments.com/0192a0c4-1b2c-7d3e-9f4a-5b6c7d8e9f0a#hw_hc_…",
  "expiresAt": "2026-10-20T14:05:00.000Z"
}

It works while the checkout can still be paid and has not expired: PENDING, REQUIRES_ACTION, or DECLINED with a card attempt left. Anything else answers 409.

StatusBodyWhen
201The response above.A new link was issued; the old one is revoked.
400Plain errorValidation failed (uuid v 7 is expected): malformed id.
404Plain errorPayment not found: <id>: unknown, another account's, or not a hosted checkout.
409Plain errorThe checkout has expired; create a new one; The checkout can no longer be paid (payment CAPTURED); create a new one (the status in parentheses is the payment's); or The checkout changed while its link was being reissued; read the payment and retry.
409HW_PROVIDER_ACCOUNT_*The account the checkout was created on no longer takes payments.
401Plain errorInvalid credentials or a disabled account.

What the shopper sees

  • The order: the line items, the subtotal, shipping, tax, discount and total, and your account's name. The e-mail you sent as customerEmail appears masked (j***@e***.com).
  • The payment form: a secure card field. Wallet buttons may also be offered depending on your account's configuration and the shopper's device; confirm with High Wire before relying on them.
  • The payer's details: on most accounts, an e-mail, the name on the card, and, for a card payment, the billing address: country, street address and city, plus the state and the postal code in the United States and Canada. You do not send these; the page points out a missing or malformed field next to it. The e-mail is optional when you sent customerEmail and a blank one means yours; otherwise it is required. On some accounts the page always asks for it: if you sent customerEmail, the shopper must type that address (in any case); if you did not and the payment page refuses the address, the shopper can enter another, up to three per checkout. An address refused by fraud screening declines the checkout for good.
  • The save box: an unticked "Save this card for future purchases" box when you created the checkout with savePaymentMethodForFutureUse: true, see Saving the card.
  • Declines: a declined card can be retried on the same page, up to five card attempts per checkout.
  • 3-D Secure: when 3-D Secure surfacing is enabled on your account and the issuer asks for it, a verification dialog opens on the page. Without it, such a card is declined and the shopper can try another.
  • Later visits: a link reopened after the payment was captured says so and offers the way back to your store; an expired unpaid link says it has expired. On some accounts, a link created before an upgrade of the page says it is no longer supported: read the payment, and once it is reconciled, create a new checkout for the shopper.

Return URLs

When the payment is captured, the page sends the shopper to successUrl with the payment id added as a query parameter, paymentId=<paymentId>. If your URL contains the literal {PAYMENT_ID}, that placeholder is replaced with the id as well. cancelUrl is where the page's "back to the store" link leads, exactly as you sent it.

The return is not proof of payment. Anyone can open your success URL. Before you fulfil the order, read the payment on your server with GET /v1/payments/{id} and check that status is CAPTURED and amount is the total you expect.

Statuses

statusFor a hosted checkout
PENDINGCreated and not paid yet, opened or not.
REQUIRES_ACTIONA card attempt is waiting on a 3-D Secure verification; the shopper can finish it from the link.
DECLINEDThe latest attempt was declined. Not necessarily final: the shopper can pay again while attempts remain and the link is live, and the same payment moves on to CAPTURED. A decline when the checkout was first used, by one of your limits or by fraud screening, is final. On some accounts a DECLINED payment can still settle CAPTURED later, so a decline alone does not make collecting again safe.
CAPTUREDPaid. From here the payment is refunded like any other.
PARTIALLY_REFUNDED, REFUNDEDA captured checkout you then refunded, in part or in full.
EXPIREDEnded unpaid once the link expired. Allow up to an hour past expiresAt; on some accounts a checkout with an attempt still being reconciled stays unresolved longer.

AUTHORIZED and VOIDED do not arise from the page's own flow, which captures in one step. CAPTURED is the status that means the payment succeeded. The payment's transactions carry each attempt and its declinedReason.

Expiry

A link stays payable until expiresAt (expiresInMinutes after creation, 24 hours by default) or until it is paid or finally declined, whichever comes first. Reissuing the link does not extend it. A checkout that is not paid in time becomes EXPIRED; create a new one if the shopper still wants to pay.

On this page