High Wire Payments

Saved payment methods

Save a card during a charge, then charge it again with the customer present, within the bounds of the payment that saved it.

A card saved during a checkout can be charged again with the customer present: an upsell or an add-on right after the purchase that saved it. There is no hosted page, no redirect, and no card number flows through your servers; you send the two opaque ids the original save returned.

POST /v1/payments/charge-saved-method

This is not a tool for unattended renewals: every reuse is bounded by the payment that first saved the card, and a charge weeks later is refused with WINDOW_CLOSED.

Obtaining the ids

Set savePaymentMethodForFutureUse: true on a card charge. Sending the flag attests that you have the customer's consent to save the card. The 201 then carries two extra fields:

{
  "paymentId": "01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b",
  "status": "CAPTURED",
  "amount": 7200,
  "cardNumber": "555555xxxxxxxx4444",
  "cardExpiration": "1133",
  "cardholderName": "Jordan Example",
  "customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
  "paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d"
}
FieldNotes
customerIdIdentifies the customer on your account, keyed on cardholderEmail (trimmed and lowercased). Persist it alongside your own customer record.
paymentMethodIdIdentifies the saved card. Pair it with customerId to charge again.
  • The ids are scoped to your account. Another account cannot use them, and a wrong pairing of customer and method is rejected.
  • Treat both ids as confidential: with your publishable key they are enough to charge the card from a browser. Keep them out of URLs, logs and client-side storage.
  • The ids are chargeable once the payment that saved the card is CAPTURED: at once when the charge answered CAPTURED, after the capture when you authorized first, and once a PENDING charge settles. Until then charge-saved-method answers 409 with reason: "ORIGINAL_NOT_SETTLED".
  • A charge that came back as a 3-D Secure challenge (status: "REQUIRES_ACTION") returns a pending save instead: charge-saved-method answers 409 with reason: "METHOD_NOT_READY" until the payment settles successfully. If the authentication fails, expires or is voided, the id is invalidated and a later charge answers 404.
  • Saving a card that is already on file for the same customer returns the existing paymentMethodId. The bounds below belong to the card, not to the id: a re-save opens nothing new.
  • A checkout through the Payment SDK can save a card too; it returns the same kind of ids, which this endpoint accepts. A hosted checkout created with savePaymentMethodForFutureUse: true can save one as well, but no API response returns its ids.

Charge a saved method

Idempotency-Key: honoured. A 409 refusal with a reason is not stored under the key.

FieldTypeRequiredRules
amountintegeryesCents, at least 100, and at most the amount of the payment that first saved the card.
customerIdstringyesUUID v7 returned by the original save.
paymentMethodIdstringyesUUID v7 returned by the original save. Must belong to customerId and to your account.
curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/charge-saved-method \
  -u "YOUR_API_KEY:YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0193c2fa-8e1b-7d5c-9a2f-3b4c5d6e7f80" \
  -d '{
    "amount": 1500,
    "customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
    "paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d"
  }'

201 Created. The response is deliberately slim: your application already has the card details from the original save.

{
  "paymentId": "0192a3f7-9c0d-7e1f-8a2b-3c4d5e6f7a8b",
  "status": "CAPTURED",
  "amount": 1500,
  "customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
  "paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d"
}
FieldNotes
paymentIdThe new payment. Persist it for refunds.
statusCAPTURED when the charge settled synchronously. PENDING when your account collects asynchronously: the payment settles to CAPTURED or DECLINED shortly after; poll it.
amountCents charged.
customerId, paymentMethodIdEcho the request.

This route never returns REQUIRES_ACTION: an issuer that insists on a 3-D Secure challenge declines the charge with declinedReason: "Additional authentication required", and the attempt counts toward the attempt limit.

Bounds

Every reuse is bounded by the saved card and by the payment that first saved it (its original payment), whichever of the card's ids is charged. Reuses made from the browser through the Payment SDK count toward the same bounds.

BoundRule
WindowAccepted up to 24 hours after the card was first saved, that is, after it became chargeable. The window never restarts, not even when the same card is saved again.
Approved reusesAt most 2 per card. A reuse still in progress holds its place; a declined one does not count.
AttemptsAt most 5 per card, declined ones included.
AmountEach reuse is at most the original payment's amount, per charge, not cumulative.

Reuse refusals

A reuse outside its bounds is refused with 409 and a machine-readable reason before the processor is contacted: nothing is charged, no payment is recorded, your limits are not consumed, and the refusal is not stored under the Idempotency-Key.

{
  "statusCode": 409,
  "message": "This saved payment method has reached its reuse limit.",
  "reason": "REUSE_LIMIT"
}
reasonmessageWhenWhat to do
ORIGINAL_NOT_SETTLEDThe original payment has not settled yet; retry once it is captured.The payment that first saved the card is still PENDING, REQUIRES_ACTION or AUTHORIZED.Retry once it is captured; the same key is fine.
METHOD_NOT_READYThe saved payment method is not ready yet; retry once its checkout is captured.The method is a pending save whose checkout has not settled (an SDK checkout, a 3-D Secure challenge).Retry once the checkout is captured; the same key is fine.
ORIGINAL_NOT_ELIGIBLEThe original payment is not in a state that allows charging this saved method again.The original payment was refunded in full, disputed, voided, declined or expired.Do not retry; collect the card again.
WINDOW_CLOSEDThe 24-hour window for charging this saved method again has closed.More than 24 hours since the card was first saved.Do not retry; collect the card again.
REUSE_LIMITThis saved payment method has reached its reuse limit.The card was already charged again twice, approved or in progress, from either surface.Do not retry; collect the card again.
ATTEMPT_LIMITThis saved payment method has reached its limit of upsell attempts.Five attempts were already made with the card, declined ones included.Do not retry; collect the card again.
AMOUNT_LIMITAn upsell cannot exceed the amount of the original payment.amount exceeds the original payment's amount.A smaller amount may pass.

A 409 without reason is an Idempotency-Key still in flight.

Ownership and state errors

These are refused before the processor is contacted, with the charge decline shape reduced to amount, status and declinedReason. No payment is recorded and your limits are not consumed.

StatusdeclinedReasonWhen
404Payment method not found.The paymentMethodId does not exist, belongs to another account, or came from a checkout that expired or was abandoned before it settled. The cases are deliberately indistinguishable.
400Payment method does not belong to the given customer.Both ids are yours, but the method belongs to a different customerId. Usually a pairing bug on your side.
400Payment method is removed., Payment method is expired., Payment method is not chargeable.The saved card is no longer chargeable and will not become chargeable again. Save the card again the next time the customer is present.
400Payment method is not chargeable on this account.The saved card can no longer be charged on your account. Save it again the next time the customer is present.
{
  "amount": 1500,
  "status": "DECLINED",
  "declinedReason": "Payment method not found."
}

Declines

When the issuer or your limits refuse the charge, the answer is 400 with the full charge decline shape. The payment is recorded as DECLINED, paymentId names it, and the attempt counts toward the five attempts (not toward the two reuses).

{
  "amount": 1500,
  "cardNumber": "555555xxxxxxxx4444",
  "cardExpiration": "1133",
  "customerId": "0192a7d1-4e2b-7f08-9a6c-2d3e4f5a6b7c",
  "paymentMethodId": "0192a7d1-8c3f-7a21-b5d4-6e7f8a9b0c1d",
  "paymentId": "0192a3f7-9c0d-7e1f-8a2b-3c4d5e6f7a8b",
  "status": "DECLINED",
  "declinedReason": "Card declined"
}
StatusBodyWhen
201The slim response above.Charged, or accepted and settling (PENDING).
400Validation erroramount below 100, or an id that is not a UUID v7.
400Charge declineAn ownership or state error (above), a limit, or the issuer.
404Charge declinePayment method not found.
409Reason-coded conflictA reuse refusal, or (without reason) an Idempotency-Key in flight.
422Plain errorThe key was reused with a different body.
429VELOCITY_LIMITA velocity check refused the charge.
401, 403Plain errorInvalid credentials or an inactive account (401); a refund-only account (403).

From the browser

The same charge can be made from your checkout page through the Payment SDK with the publishable key, so it works after a page reload. It is bounded by the same window, limits and amount, and the counts are shared: a reuse from the browser and one from this endpoint are the card's two. A reuse the browser gets back as PENDING is followed from your server with GET /v1/payments/{id}; the browser holds no credential to read it. See hw.chargeSavedMethod() in the Payment SDK reference.

On this page