High Wire Payments

Integration guide

Initialize Payment SDK 0.7.0, mount checkout, and handle results and retries.

Use this guide for the checkout flow. Choose Plain HTML or React / Next.js for a complete example, and use the API Reference for parameters and callbacks.

1. Install and initialize

Load the pinned CDN build, which includes checkout and wallet loaders:

<script src="https://cdn.highwirepayments.com/sdk/0.7.0/highwire.js"
        integrity="sha384-NZVoZbDdfN3Ix90/7E+1ojIBysBz1Cwhizp9zPhjUxaOVkMdHBk9RSSxMSAO44LU"
        crossorigin="anonymous"></script>
<script src="/checkout.js" defer></script>

Inside an async browser function or module, await initialization:

const HighWire = window.HighWire.default;
const hw = await HighWire.init({
  publishableKey: 'REPLACE_WITH_YOUR_TEST_PUBLISHABLE_KEY',
  apiBase: 'https://api-payments-staging.highwirepayments.com',
});

init() resolves once the checkout's device session is ready, or after at most 2 seconds while it keeps loading; mount() then waits at most 2 seconds more for the session key. A screening failure never rejects either.

Use the matching key and gateway URL issued during onboarding, and register your checkout origins. See Security & server verification.

For TypeScript declarations, install the matching npm version:

npm install @highwirepayments/payment-sdk@0.7.0

With this CDN runtime, use type-only npm imports:

import type { Checkout, PaymentResult } from '@highwirepayments/payment-sdk';

2. Mount checkout at the payment step

Before mounting, your backend must reconcile any existing order payment and authorize a new attempt. Then call hw.mount() with the server's order amount in integer cents and USD currency. It creates or reuses a payment; mount one checkout at a time per instance when the shopper reaches payment.

const checkout = await hw.mount({
  container: '#payment',
  amount: 2999,
  currency: 'usd',
  onResult: (result) => console.log(result.status),
  onError: (error) => console.error(error.code),
});

This is an API fragment. The framework guides show the complete controls and binding flow. Keep the checkout container inert until your backend has verified and bound checkout.paymentId to the order. Wallets and drop-in forms with their own button can pay through their own controls.

mount() takes no shopper email: pass it to submit(). In plain JavaScript, a customerEmail passed to mount() is ignored.

The session selects the checkout layout. Mount the whole checkout; there are no individual element controllers.

3. Submit and handle results

Show your Pay button only when checkout.requiresSubmit is true: for the card field, and for a drop-in form that uses your Pay button. Enable it after binding, while no operation is running, and while the form can take a payment:

CheckoutEnable Pay while
Card fieldonChange reports complete: true.
Drop-in form that uses your Pay buttononAvailabilityChange reports true. Disable it again on false.

A checkout reports only one of the two. onReady alone does not mean the form can take a payment; form-loading failures can occur after mount() resolves.

Call checkout.submit({ customerEmail }) from your Pay button with the shopper's email. A card submit() without a valid email rejects HW_INCOMPLETE before anything is sent. The payer details also accept phone and billing address. A drop-in form that uses your Pay button also needs the full billing address, with at least name, a two-letter country, and postalCode, unless you mount it with billingAddressForm: 'full'. Wallets and drop-in forms with their own button share onResult.

submit() resolves with the same result delivered to onResult: handle it once. Declines resolve; operational errors reject.

ResultAction
succeededShow confirmation; fulfill only after server verification.
declined, retryable: trueKeep checkout open; allow another card or method on the same payment.
declined, retryable: falseCheckout ended; reconcile before another attempt.
pendingCheckout ended with an unresolved payment; check order status on your server.

A card decline in a drop-in form that uses your Pay button also resolves declined and reaches onResult: retryable: true keeps the checkout open for another card, and retryable: false ends it. When that form refuses the card details, billing address, or email, submit() rejects HW_INCOMPLETE with recovery correct: let the shopper correct them and pay again. See errors with recovery.

On that form, HighWire screens the shopper's email and binds it to the payment before the card is charged. A later submit() without an email reuses the email of the payment's last declined attempt; before any decline, it rejects HW_INCOMPLETE. The shopper can correct the email, and each new one is screened again. A payment accepts at most 3 different emails.

3-D Secure

Keep checkout mounted and Pay disabled during verification. Closing the challenge rejects HW_3DS_ABANDONED and ends checkout, but the payment can still capture: reconcile it on your server. A timeout or challenge that could not open can be retried with submit() on the same checkout. That retry may reopen the original challenge and pay with the original card, even after field changes; saved-card and masked-card fields can be absent from its result.

A drop-in form that uses your Pay button runs verification inside the form instead, and HW_3DS_ABANDONED does not apply. Closing its verification dialog keeps the attempt, and the form offers to finish verification. A full-page verification step can take the shopper's tab away and back to HighWire's return page, so your page loses its state: settle the order from your server's payment read or webhook.

4. Order changes and cleanup

Call checkout.unmount() when leaving checkout. It removes UI without cancelling a charge. After unmounting, an open payment can be reused on the same instance with the same amount, currency, and savePaymentMethodForFutureUse. Currency is case-insensitive, and an omitted save flag is equivalent to false. Changing the amount or currency creates another payment. Changing only the save option creates another payment once the last one can no longer capture. On a drop-in form that uses your Pay button, a different amount or save option rejects HW_TOKENIZE_IN_PROGRESS while its payment may still capture.

The SDK accepts no merchant order ID, and the email no longer tells two orders apart: two orders with the same amount reuse the same payment. For a different business order with identical options, use a fresh instance after your backend authorizes it. Preserve order/payment bindings across reloads.

If unmounting interrupts an unanswered card confirmation, submit rejects HW_DESTROYED. Recover by remounting the same normalized options on the same instance and retrying submit. SDK-generated attempt keys do not survive a reload; after reloading, recover payment status on your server before authorizing another attempt.

A submission from a drop-in form that uses your Pay button survives unmount() while its outcome is unknown. On the same instance, mounting different options then rejects HW_TOKENIZE_IN_PROGRESS until checkout.recheck() reads the payment succeeded; a declined or expired read does not lift it. The same options reuse its payment for a status check only: submit() never sends it again. After a pending result, the same options reject HW_SESSION_EXPIRED. Once your server has reconciled that payment and authorizes a new one, start it on a fresh instance, for example after a reload.

The same holds after that form's submit() rejects HW_DECLINED with recovery recheck, because HighWire denied the shopper's email, or HW_TOKENIZE_IN_PROGRESS with recovery recheck, because HighWire refused a changed email or save box while the payment was in progress. The checkout keeps the original payment: a later submit() only reads it, for up to 3 minutes, resolving succeeded if it captured and pending otherwise. A read that fails for good rejects HW_SERVICE_UNAVAILABLE with recovery recheck. Even a declined read never permits a replacement payment on that instance.

After pending, abandoned verification, or any unknown outcome, reconcile before remounting. The SDK permitting a new mount is not proof that the earlier payment failed.

5. Errors and retries

Mount and submit rejections also reach onError; show each error once. Keep a submit catch because calls after unmount only reject. Use your own shopper-facing copy rather than raw responses.

  • Transient mount failure: retry HW_NETWORK, HW_TIMEOUT, or HW_SERVICE_UNAVAILABLE. Back off on HW_RATE_LIMITED; stop on declined, unauthorized, or invalid configuration.
  • Lost submit answer: retry the same checkout's submit() to retain its attempt key. Back off on rate limiting. Do not switch instances or methods to bypass an unresolved attempt.
  • Form-loading failure: unmount the unusable form and reconcile before remounting. An asynchronous form HW_TIMEOUT needs this recovery, unlike a rejected mount timeout.
  • Ended checkout: check payment on your server before enabling another attempt.

See the complete error-code table for input, operation-state, and saved-card recovery.

Errors with recovery

Errors from a drop-in form that uses your Pay button carry error.recovery. Act on it before the code:

  • correct: the payer details, the email, or the card or billing fields in the form, were refused. This includes an email HighWire refuses and a fourth different email on one payment. Let the shopper correct them, then submit again.
  • retry: the payment was not sent. Allow another submit() while onAvailabilityChange reports true.
  • wait: another operation is still running. Keep Pay disabled; its result or error follows.
  • recheck: the payment may still capture: its outcome is unknown, HighWire denied the shopper's email (HW_DECLINED), or it refused a change during the payment (HW_TOKENIZE_IN_PROGRESS). Keep Pay disabled and tell the shopper the payment is being confirmed. Check it on your server, or call checkout.recheck() once the running submit() settles. Another submit() only checks the same payment; it never sends it again.
  • unavailable: this checkout can't take a payment. Stop its controls and check the order on your server.

A recheck error can reach onError while submit() is still confirming the payment, so the result that follows still counts. See CheckoutRecovery.

Saving a card

Optional: save and reuse a card

Mount with savePaymentMethodForFutureUse: true to show an unticked save box in the card field, or under the card form of a drop-in form that uses your Pay button. Only the shopper's tick saves the card, under the email submit() sends, and HighWire finds or creates the customer then. A drop-in form with its own button and wallets show no box and save no card.

The result carries customerId and paymentMethodId only when the shopper ticked the box; on a drop-in form that uses your Pay button, only on succeeded. A card the shopper saved before keeps its ID. Changing the box while that form's payment is in progress rejects HW_TOKENIZE_IN_PROGRESS with recovery recheck.

Use the returned paymentMethodId with hw.chargeSavedMethod(). A card saved through a drop-in form that uses your Pay button can resolve pending: settle it from your server. If its issuer asks for 3-D Secure again, the charge does not capture, because there is no shopper step for it.

Store a stable idempotency key with each add-on charge and reuse it across retries and reloads. An unknown outcome must be recovered with that key. After a confirmed amount refusal, lowering the amount is a new charge and needs a new key. A saved-card rate refusal retained under its key also requires reconciliation before a later charge with a new key. Verify every result on your server.

Upgrading from 0.6.0

Update the script URL, its SRI hash, the npm types, and any SDK_VERSION check, then:

  • Move customerEmail from mount() to submit(). Call checkout.submit({ customerEmail }). A card submit() without an email now rejects HW_INCOMPLETE, and TypeScript rejects customerEmail in mount() options.
  • Replace hw.riskSessionKey(). The instance has only mount() and chargeSavedMethod(). mount() still attaches the device-session key to its own payments. For Direct API charges, take the key from the collector; see Device screening for Direct API charges.
  • Card saving. savePaymentMethodForFutureUse no longer needs an email. It shows an unticked save box, and only the shopper's tick saves the card; see Saving a card.
  • Drop-in forms that use your Pay button. A card decline now resolves declined instead of rejecting HW_INCOMPLETE. A submit() email that differs from an earlier one is screened again instead of rejecting HW_UNSUPPORTED. Send the full billing address, including postalCode.
  • HighWire.init() resolves within 2 seconds instead of waiting up to about 14 for the device session.

0.7.0 needs HighWire's matching service release, so coordinate the upgrade with HighWire:

Checkout0.6.0 against the new release0.7.0 against an older release
Drop-in form that uses your Pay buttonFails through onError with HW_UNSUPPORTED after mount() resolves and takes no payment.Fails through onError with HW_UNSUPPORTED after mount() resolves and takes no payment.
Card field savingNever shows the save box and saves no card.Shows the save box, but a tick saves nothing.
Card fields, wallets, drop-in forms with their own buttonKeep paying.Keep paying.

Upgrading from 0.5.0

This section describes the move to 0.6.0. Upgrade straight to 0.7.0 and apply Upgrading from 0.6.0 as well: 0.7.0 replaces the email rules below.

Card field, wallet, and other drop-in integrations need no code change: update the script URL, its SRI hash, the npm types, and any SDK_VERSION check.

Some account configurations use a drop-in form that takes payment through your Pay button instead of its own. On those checkouts:

  • checkout.requiresSubmit is now true. Show your Pay button, enable it from onAvailabilityChange, and call checkout.submit(). Without your own Pay button, the checkout takes no payment.
  • mount() requires customerEmail and rejects HW_UNSUPPORTED without it. submit() must use the same email.
  • submit() requires a billingAddress with name and a two-letter country. Alternatively, mount with billingAddressForm: 'full' to collect the address inside the form.
  • submit() resolves succeeded or pending, and errors carry recovery; see errors with recovery.

These checkouts need SDK 0.6.0 and HighWire's matching service release together. Once your environment serves that release, 0.5.0 and earlier fail on them through onError and take no payment. 0.6.0 against an environment that doesn't serve it yet fails the same way with HW_UNSUPPORTED. Coordinate the upgrade with HighWire.

Testing and support

Use the issued test key and staging gateway. Obtain test cards from your integration engineer. Exercise success, retryable and terminal declines, pending, network retries, verification, cleanup, and enabled wallets. Check CSP before enforcing it, and follow safe diagnostics when contacting support.

On this page