Errors and conventions
Every request convention, HTTP status code, response body shape and retry rule of the merchant API.
This page covers what is common to every endpoint: request conventions, status codes, the exact JSON shapes the API answers with, and what to do on each. The endpoint guides link here instead of repeating the shapes.
Conventions
| Topic | Rule |
|---|---|
| Base URL | https://api-payments-staging.highwirepayments.com/v1 on staging, https://api-payments.highwirepayments.com/v1 in production. See Environments. |
| Authentication | HTTP Basic on every request. See Authentication. |
| Format | JSON request and response bodies. Send Content-Type: application/json. A request body is limited to 100 kB. |
| Success status | Every POST answers 201 Created, including void, capture and link reissue. GET answers 200 OK. |
| Amounts | Integers in cents, the smallest unit of the currency: 7200 is $72.00. Never decimals, never strings. |
| Currency | USD only. Card charges carry no currency field, the hosted checkout requires "usd", and a retrieved payment always reports "usd". |
| Identifiers | Payment, refund, customer and payment-method ids are UUID v7 strings. Routes that take a payment id in the path answer 400 for anything that is not a UUID v7; the refund route answers 404 instead. |
| Card numbers in responses | Masked as the first six digits, eight x and the last four: 555555xxxxxxxx4444. A card number shorter than 16 digits shows only its last four. The full number is never echoed. |
| Timestamps | ISO 8601 in UTC with milliseconds, for example 2026-10-19T14:05:00.000Z. |
| Unknown fields | Silently dropped, never rejected. A misspelled optional field is not an error, so check the response rather than relying on a rejection. |
| Types | Validated strictly. A number sent as a string ("7200") fails validation. |
| Optional fields | Omit the ones you do not use. The hosted checkout also accepts null as "absent". |
| Versioning | /v1 is the current stable version. Future versions will be served alongside it, with notice before any deprecation. |
HTTP status codes
| Status | Meaning | Body |
|---|---|---|
200 | A read succeeded. | The payment, see Payment lifecycle. |
201 | A write succeeded. For a charge this includes PENDING (accepted, settling) and REQUIRES_ACTION (the customer must authenticate). | The endpoint's response object. |
400 | Invalid input, a declined charge, refund, void or capture, a business rule, or a feature not enabled for the account. | Validation error, Charge decline, Refund decline, Void and capture decline, Plain error or Code-carrying error. |
401 | Missing or invalid credentials, or an inactive account. | Plain error. See Authentication. |
403 | The account is limited to refunds and voids and called another route. | Plain error. |
404 | Unknown id, another account's resource, or a payment that is not in a state the route can act on. | Plain error. On charge-saved-method the body is a Charge decline. |
409 | A rule refused the call before anything was charged, a hosted checkout link cannot be reissued, a request with the same Idempotency-Key is still in flight, or no account of yours can take the payment. | Reason-coded conflict, Plain error or Code-carrying error. |
422 | The Idempotency-Key was reused with a different request body. | Plain error. |
429 | A velocity check refused a card charge. | Code-carrying error with code: "VELOCITY_LIMIT". |
5xx | The gateway failed. | Plain error. Retry with the same Idempotency-Key, unless the operation was accepted; see Server errors. |
Response body shapes
Validation error
400, returned when a required field is missing or a value is malformed. fieldErrors maps each
offending field path to its problems. Nested paths use dots and array indexes are numbers:
deviceData.device.ip, lineItems.0.name.
{
"statusCode": 400,
"message": "Some fields are invalid, please check the errors",
"fieldErrors": {
"amount": ["amount must not be less than 1", "amount must be an integer number"],
"cardNumber": ["cardNumber must be 12-19 digits with no spaces or separators"]
}
}A validation error is stored under the Idempotency-Key like any other 4xx: fix the body and
send it under a new key, otherwise the corrected request answers 422.
Plain error
The default shape for everything that is neither a decline nor a validation failure: 401, 403,
404, business-rule 400s, malformed ids, idempotency 409/422 and 5xx.
{
"statusCode": 404,
"error": "Not Found",
"message": "Payment not found: 01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b"
}message is human-readable. It is stable enough to log and to show to an operator, but branch on
the status code and the route, not on the text. Messages you will meet:
| Status | message | Where |
|---|---|---|
400 | Validation failed (uuid v 7 is expected) | A payment id in the path that is not a UUID v7. |
400 | Idempotency-Key must be 1..255 characters, Idempotency-Key header may only appear once per request | Any idempotent route. |
400 | Payment is not in a voidable state; only an in-flight or authorized (uncaptured) payment can be voided. Try to refund the payment instead. | Void. |
400 | This payment has already been captured. | Capture. |
400 | Payment has already been fully refunded, A refund is already in progress for this payment | Refunds. |
401 | Missing Authorization header, Invalid credentials | Authentication. |
403 | Merchant account can only process refunds and voids | Authentication. |
404 | Payment not found: <id>, Payment not found or not refundable: <id> | Unknown payment, another account's, or (on refund and capture) a payment without a successful capture or authorization. |
409 | A request with this Idempotency-Key is already in progress | Idempotency key. |
409 | The checkout has expired; create a new one | Hosted checkout. |
422 | Idempotency-Key was previously used with a different request body | Idempotency key. |
500 | Internal server error | Unexpected failure. |
Charge decline
400 from auth-and-capture, auth and charge-saved-method. The charge was well-formed and was
refused: by your account limits, by fraud screening, or by the card's issuer. There is no
statusCode in this body; the HTTP status carries it. The payment is recorded with
status: DECLINED so it appears in your reporting, but a declined direct charge does not return
its paymentId.
{
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444",
"cardExpiration": "1133",
"cardholderName": "Jordan Example",
"status": "DECLINED",
"declinedReason": "Card declined"
}| Field | Notes |
|---|---|
cardholderName | Only on the raw-card routes. |
paymentId, customerId, paymentMethodId | Only on charge-saved-method declines, where the declined attempt counts toward the card's attempt limit. |
declinedReason | One of a fixed set of short reasons, listed under Declines. Log it and decide on status; show the shopper a generic message. |
charge-saved-method also uses this shape, without card fields, for an unknown payment method
(404 with declinedReason: "Payment method not found.") and for ownership problems (400), see
Saved payment methods.
Refund decline
400 from refund when the processor refused the refund or your daily refund limit blocked it.
status is the refund's status, not the payment's.
{
"paymentId": "01928e3e-7b6c-7c8e-9b3a-5b9f8b9b9b9b",
"amount": 500,
"cardNumber": "555555xxxxxxxx4444",
"status": "FAILED",
"declinedReason": "Amount exceeds daily refund limit"
}cardNumber is null for wallet payments, which carry no card number.
status is CANCELED only in the rare case where the refund was already cancelled on the
processor's side before the call finished; no money moved either way.
Void and capture decline
400 from void and capture when the processor refused the operation. Here status: "DECLINED"
describes the attempt, not the payment: read the payment to see where it stands.
{
"paymentId": "01929a10-5d2e-7f3b-8c4d-1e2f3a4b5c6d",
"amount": 7200,
"cardNumber": "555555xxxxxxxx4444",
"status": "DECLINED",
"declinedReason": "Capture declined"
}Reason-coded conflict
409 with a machine-readable reason. The rule fired before anything was charged or recorded, and
the refusal is not stored under the Idempotency-Key, so the same key can be sent again once
the condition clears.
{
"statusCode": 409,
"message": "This saved payment method has reached its reuse limit.",
"reason": "REUSE_LIMIT"
}reason | Route | Meaning |
|---|---|---|
ORIGINAL_NOT_SETTLED, METHOD_NOT_READY, ORIGINAL_NOT_ELIGIBLE, WINDOW_CLOSED, REUSE_LIMIT, ATTEMPT_LIMIT, AMOUNT_LIMIT | charge-saved-method | The reuse is outside its bounds. See Reuse refusals. |
PAYMENT_DISPUTED | refund | The payment has an open or lost dispute. See Refunds and disputes. |
A 409 without reason is a plain error: an Idempotency-Key still in flight, or a hosted
checkout link that cannot be reissued.
Code-carrying errors
These errors carry a stable code.
HW_UNSUPPORTED (400): the hosted checkout is not enabled for your account. Nothing is written.
{
"statusCode": 400,
"error": "Bad Request",
"code": "HW_UNSUPPORTED",
"message": "Hosted checkout is not enabled for this account"
}HW_NO_ACTIVE_PROVIDER_ACCOUNT (409): no account of yours takes new payments. On a payment that
already exists, HW_PROVIDER_ACCOUNT_REFUND_ONLY, HW_PROVIDER_ACCOUNT_RESTRICTED,
HW_PROVIDER_ACCOUNT_DISABLED or HW_PROVIDER_ACCOUNT_UNRESOLVED (409): the account it was
created on no longer takes payments. Neither is stored under an Idempotency-Key, so the same key
can be retried once High Wire has resolved the account.
{
"statusCode": 409,
"error": "Conflict",
"code": "HW_NO_ACTIVE_PROVIDER_ACCOUNT",
"message": "No active provider account can take this payment."
}VELOCITY_LIMIT (429): a velocity check refused a card charge. Nothing is charged and no payment
is recorded. There is no Retry-After header. See Account limits.
{
"statusCode": 429,
"message": "Velocity limit exceeded",
"code": "VELOCITY_LIMIT"
}Server errors
A 5xx is a plain error. The idempotency record for the call is discarded, so
retry with the same Idempotency-Key; a retry under the same key cannot charge twice. A retry
can answer a plain 400 such as This payment has already been captured. when the first attempt
did go through; read the payment to confirm.
A 500 whose message says the operation was accepted (Capture accepted; finalization pending. Poll the payment., and the same for a void or a refund) means exactly that: do not retry; poll the
payment until it reflects the operation.
Telling the shapes apart
Check the body in this order; the first match wins.
fieldErrorspresent: a validation error.declinedReasonpresent: a decline.statussays which kind:DECLINEDfor a charge, void or capture;FAILEDorCANCELEDfor a refund.reasonpresent: a reason-coded conflict.codepresent: a code-carrying error.- Otherwise a plain error; use
statusCode.
Retrying
| Situation | What to do |
|---|---|
| Network timeout or connection error on a write | Retry with the same Idempotency-Key and body. |
5xx | Retry with the same key; then read the payment. If the message says the operation was accepted, do not retry: poll the payment. See Server errors. |
409 without reason | The first call is still running. Retry the same key after a moment to get its outcome. |
409 with reason | Act on the reason. Some clear on their own (ORIGINAL_NOT_SETTLED, METHOD_NOT_READY, PAYMENT_DISPUTED once won); the others need a new card or a smaller amount. |
422 | A client bug: the key was reused for a different operation. Use a new key for the new operation. |
429 | Stored under the key. Do not retry the same request; a new attempt needs a new key, later. |
400 decline | Stored under the key. A new attempt is a new operation with a new key. |
400 validation | Stored under the key. Fix the body and use a new key. |
401, 403 | Fix the credentials or contact High Wire; nothing was stored. |
201 with status: PENDING | Do not retry. Poll the payment; see Polling. |
Idempotency key
Retry writes safely with the Idempotency-Key header: scope, replays, the 409 and 422 answers, and the rule for 5xx.
Authorize a payment POST
Authorizes the requested amount and leaves the payment in the AUTHORIZED state for later capture or void. The request can optionally save the payment method for future use. When the issuer requires 3-D Secure, the call returns 201 with status REQUIRES_ACTION and a nextAction redirect for the customer; the final state settles asynchronously (poll GET /payments/:id).