Card payments
Charge cards from your server: authorize and capture in one call, authorize then capture or void, 3-D Secure, device data, and every decline.
The direct API takes card details from your server and charges them. Use it only when your environment is validated to handle card data. Otherwise collect the card in the browser with the Payment SDK, or hand the shopper a hosted checkout link and never touch the card at all.
Card data and PCI DSS
Sending card numbers to this API puts your systems in scope for PCI DSS (typically SAQ D). Use test cards only in examples and tooling, and never put real card data in a shell, its history, logs, tickets or chat. Never log request bodies, never store the security code, and keep card data in memory only for as long as the request needs it.
| Flow | Endpoints | When |
|---|---|---|
| Authorize and capture | POST /v1/payments/auth-and-capture | The common case: charge the card now. |
| Authorize, then capture or void | POST /v1/payments/auth, then POST /v1/payments/{id}/capture or POST /v1/payments/{id}/void | Hold the funds first (stock check, manual review, charge on shipment), then settle or release them. |
Both charge routes take the same request body and answer the same shape. Money that was captured is returned with a refund; an authorization that was not captured is released with a void.
Availability. Not every account can use the direct card routes; High Wire confirms at onboarding which surfaces yours supports. A route your account cannot use answers a charge decline that says so, see Declines.
Request body
Shared by auth-and-capture and auth.
| Field | Type | Required | Rules |
|---|---|---|---|
amount | integer | yes | Cents, at least 1. 7200 is $72.00. |
cardNumber | string | yes | 12 to 19 digits, no spaces or separators. |
cardExpiration | string | yes | MMYY: 1133 is November 2033. |
cardCvv | string | yes | The card's security code. Never stored. |
cardholderName | string | yes | Name on the card. |
cardholderEmail | string | yes | The customer's e-mail, trimmed and lowercased. When the card is saved, this is what identifies the customer. The format is not validated. |
cardholderPhone | string | yes | E.164 recommended, for example +14155550101. |
cardholderBillingAddress | string | yes | Street address. |
cardholderBillingZipcode | string | yes | Postal code. |
cardholderBillingCity | string | yes | City. |
cardholderBillingState | string | yes | Two-letter US state code. |
savePaymentMethodForFutureUse | boolean | no | Default false. true saves the card and returns customerId and paymentMethodId. Sending it attests that you have the customer's consent. See Saved payment methods. |
deviceData | object | no | Browser signals for your payment processor, validated strictly when present. See Device data. |
riskSessionKey | string | no | The value riskSessionKey() returns on the Payment SDK's device-screening collector (HighWireCollect({ publishableKey, apiBase })), forwarded unchanged (at most 100 characters). Omit it when the collector returns null. |
The string fields are checked for presence only, except where a rule is listed. Unknown fields are dropped silently. A failed rule answers a validation error naming the field.
Device data
deviceData carries signals about the cardholder's browser for your payment processor, where
they improve authorization rates and reduce 3-D Secure friction.
{
"deviceData": {
"device": {
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) …",
"location": { "lat": 37.7749, "lon": -122.4194 }
}
}
}| Path | Type | Required when deviceData is present | Rules |
|---|---|---|---|
device | object | yes | |
device.ip | string | yes | A well-formed IPv4 or IPv6 address, at most 45 characters. It must be the cardholder's address as seen from their browser, not your server's. |
device.userAgent | string | yes | The browser's User-Agent, non-empty, at most 1024 characters. |
device.location | object | no | { "lat": number, "lon": number } in decimal degrees: lat from −90 to 90, lon from −180 to 180. |
device.deviceFingerprint | string | no | Reserved for integrations agreed with High Wire; most integrations never set it. Letters, digits, _ and -, at most 128 characters. |
- Collect the values in the browser with the Payment SDK's
device-screening collector. Any extra keys it adds are
dropped by the gateway. An embedded Payment SDK checkout sends no
deviceData: HighWire reads the browser's address and location itself. - Omit
deviceDataentirely when you have nothing to send. Never send an empty object or a hand-built substitute.
Authorize and capture
POST /v1/payments/auth-and-captureIdempotency-Key: honoured.
curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/auth-and-capture \
-u "YOUR_API_KEY:YOUR_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0193c2e9-4f9a-7c2f-9c83-1b2b3c4d5e6f" \
-d '{
"amount": 7200,
"cardNumber": "5555555555554444",
"cardExpiration": "1133",
"cardCvv": "456",
"cardholderName": "Jordan Example",
"cardholderEmail": "jordan@example.com",
"cardholderPhone": "+14155550101",
"cardholderBillingAddress": "100 Example Street",
"cardholderBillingZipcode": "94105",
"cardholderBillingCity": "Springfield",
"cardholderBillingState": "CA",
"riskSessionKey": "01928e1c-2a4d-7c8e-8b1a-1234567890ab"
}'201 Created:
{
"paymentId": "01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b",
"status": "CAPTURED",
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444",
"cardExpiration": "1133",
"cardholderName": "Jordan Example"
}| Field | Type | Notes |
|---|---|---|
paymentId | string | Persist it. Every later call about this payment (read, capture, void, refund) uses it. |
status | enum | CAPTURED: done. PENDING: the processor accepted the charge and is still settling it; poll. REQUIRES_ACTION: the customer must authenticate, see 3-D Secure. |
amount | integer | Cents captured. |
cardNumber | string | Masked. |
cardExpiration | string | MMYY. |
cardholderName | string | As sent. |
customerId, paymentMethodId | string | Only when savePaymentMethodForFutureUse was true. See Saved payment methods. |
nextAction | object | Only when status is REQUIRES_ACTION. |
| Status | Body | When |
|---|---|---|
201 | The response above. | Captured, pending or awaiting the customer. |
400 | Validation error | A field failed its rule. |
400 | Charge decline | Refused by a limit, by fraud screening or by the issuer. See Declines. |
400 | Plain error | Failed to register customer for saved payment method.: the save flag was set and the customer could not be registered. No payment was created. |
401, 403 | Plain error | Invalid credentials or an inactive account (401); a refund-only account (403). |
409, 422 | Plain error | Idempotency key in flight or reused with a different body. |
429 | VELOCITY_LIMIT | A velocity check refused the charge. |
Authorize now, capture later
POST /v1/payments/authIdempotency-Key: honoured. Same request body and the same responses as above; a success settles
to AUTHORIZED instead of CAPTURED:
{
"paymentId": "01929a10-5d2e-7f3b-8c4d-1e2f3a4b5c6d",
"status": "AUTHORIZED",
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444",
"cardExpiration": "1133",
"cardholderName": "Jordan Example"
}The funds are held on the card. Capture them to settle, or void to release them. An authorization
that is neither captured nor voided eventually lapses at the processor and the payment becomes
EXPIRED; how long a hold lasts is decided by the card networks, so capture as soon as you can.
Capture
POST /v1/payments/{id}/captureIdempotency-Key: honoured. No request body. Captures the full authorized amount; partial captures are not supported.
curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/01929a10-5d2e-7f3b-8c4d-1e2f3a4b5c6d/capture \
-u "YOUR_API_KEY:YOUR_SECRET" \
-H "Idempotency-Key: 0193c2f1-2b7d-7a4e-8f10-6c7d8e9f0a1b"201 Created:
{
"paymentId": "01929a10-5d2e-7f3b-8c4d-1e2f3a4b5c6d",
"status": "CAPTURED",
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444"
}| Status | Body | When |
|---|---|---|
201 | The response above. | Captured. |
400 | Plain error | Payment is not in a capturable state; the authorization must be successful., The authorization is no longer an open hold and cannot be captured., This payment has already been captured., This payment is being voided and can no longer be captured., or Validation failed (uuid v 7 is expected) for a malformed id. |
400 | Void and capture decline | The processor refused the capture. |
404 | Plain error | Payment not found: <id>: unknown, another account's, or a payment with no authorization to capture. A payment made with auth-and-capture answers 404 here, since it was captured on creation. |
401, 403, 409, 422 | Plain error | As for a charge. Refund-only accounts cannot capture. |
500 | Plain error | The capture was accepted but could not be finalized yet: do not retry; poll the payment. |
Void
POST /v1/payments/{id}/voidIdempotency-Key: ignored. No request body. Cancels, for its full amount, a payment that has not
been captured: an uncaptured AUTHORIZED hold, a charge still PENDING at the processor, or a
REQUIRES_ACTION payment whose customer abandoned the 3-D Secure challenge. A captured payment
cannot be voided; refund it instead. Accounts limited to refunds and voids may call
this route.
curl -X POST https://api-payments-staging.highwirepayments.com/v1/payments/01929a10-5d2e-7f3b-8c4d-1e2f3a4b5c6d/void \
-u "YOUR_API_KEY:YOUR_SECRET"201 Created:
{
"paymentId": "01929a10-5d2e-7f3b-8c4d-1e2f3a4b5c6d",
"status": "VOIDED",
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444"
}cardNumber is null for payments that carry no card number, such as wallet payments made
through a checkout.
| Status | Body | When |
|---|---|---|
201 | The response above. | Voided. |
400 | Plain error | Payment is not in a voidable state; only an in-flight or authorized (uncaptured) payment can be voided. Try to refund the payment instead., The payment can no longer be voided. Try to refund the payment instead., Payment has already been captured. Try to refund the payment instead., Payment has already been voided., A void is already in progress for this payment, or Validation failed (uuid v 7 is expected). |
400 | Void and capture decline | The processor refused the void. |
404 | Plain error | Payment not found: <id>: unknown, another account's, or a payment that never reached the processor (for example a hosted checkout nobody has opened). |
401 | Plain error | Invalid credentials, or an inactive account. |
500 | Plain error | The void was accepted but could not be finalized yet: do not retry; poll the payment. |
3-D Secure
3-D Secure surfacing is enabled per account by High Wire, together with your 3-D Secure
return page (an HTTPS page on your site the customer lands on after the challenge). Until it is
enabled, a charge whose issuer demands a challenge is declined with
declinedReason: "Additional authentication required". Enable it once your integration renders
nextAction and polls for the outcome.
When it is enabled and the issuer requires the customer to authenticate, the charge still answers
201, with status: "REQUIRES_ACTION" and a nextAction instead of a settled outcome. Nothing has
been charged yet; the payment waits for the customer.
{
"paymentId": "01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b",
"status": "REQUIRES_ACTION",
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444",
"cardExpiration": "1133",
"cardholderName": "Jordan Example",
"nextAction": {
"kind": "REDIRECT",
"url": "https://verify.example.com/3ds/s/7Qx2LwP7aZ0cT5uYbR1d",
"method": "GET"
}
}| Field | Notes |
|---|---|
kind | REDIRECT: open url with a GET, in an iframe or by navigating the page. REDIRECT_FORM: post data to url as an auto-submitted HTML form. |
url | The challenge URL. Opaque and time-limited: do not parse, rewrite or store it beyond the pending window. |
method | GET for REDIRECT, POST for REDIRECT_FORM. |
data | REDIRECT_FORM only: the form fields to post. |
fallbackUrl | Sometimes present: an alternate URL when the primary one cannot be rendered. |
Integration flow:
- Render the challenge, typically an iframe whose
srcisnextAction.url. - The customer completes or fails it. The frame is then sent to your configured return page with a few query parameters describing the outcome. Treat them as a hint for your UI only, never as the settled outcome: anyone can open that page.
- Poll
GET /v1/payments/{id}. It keeps serving the pendingnextActionuntil the payment settles toCAPTUREDorAUTHORIZEDon success,DECLINEDwhen authentication fails (the transaction'sdeclinedReasonisAuthentication failed), orEXPIREDwhen the challenge times out unanswered.
Notes:
- A replay under the same
Idempotency-Keyreturns the same response, including the same challenge URL. - An abandoned challenge can be cancelled with void, which settles the payment to
VOIDED. - When
savePaymentMethodForFutureUsewastrue, the response carriescustomerIdandpaymentMethodId, but the method is a pending save: it becomes chargeable only once the payment settles successfully, and it is invalidated if the authentication fails, expires or is voided. See Saved payment methods. charge-saved-methodnever returnsREQUIRES_ACTION; an issuer that insists on a challenge there declines the charge.
Declines
A decline is a 400 with the charge decline body.
The payment is stored with status: DECLINED for your reporting. The response is stored under the
Idempotency-Key, so a new attempt is a new operation with a new key.
declinedReason | Source | What to do |
|---|---|---|
Card declined, Insufficient funds, Card expired, Incorrect card details, Card not supported, Duplicate transaction, Processing error. Try again later., Payment processing is unavailable. Please contact support., Payment declined | The card's issuer or the processor refused the charge. declinedReason is always one of these fixed strings. | Show the shopper a generic decline message, keep declinedReason for your logs, and let them try another card. |
Additional authentication required | The issuer wants 3-D Secure and it could not be surfaced: 3-D Secure is not enabled on the account, or the route cannot show a challenge (charge-saved-method). | Enable 3-D Secure surfacing, or collect the card through the Payment SDK or the hosted checkout, which run the challenge for you. |
Amount exceeds per-transaction limit, Amount exceeds daily limit, Amount exceeds monthly limit | Your merchant limits, checked before the processor is contacted. | Ask High Wire to raise the cap ahead of time. |
Transaction flagged as high risk. | The charge was refused; no money moved. | Handle it like any other decline. |
auth-and-capture is not available for your account. Please contact support., auth is not available for your account. Please contact support. | Your account cannot use the direct card routes. | Use the Payment SDK or the hosted checkout. |
A charge the processor accepted is never answered with a decline: it answers 201 with
status: "PENDING" and settles shortly after. Do not retry a PENDING payment; poll it.