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.0With 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:
| Checkout | Enable Pay while |
|---|---|
| Card field | onChange reports complete: true. |
| Drop-in form that uses your Pay button | onAvailabilityChange 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.
| Result | Action |
|---|---|
succeeded | Show confirmation; fulfill only after server verification. |
declined, retryable: true | Keep checkout open; allow another card or method on the same payment. |
declined, retryable: false | Checkout ended; reconcile before another attempt. |
pending | Checkout 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, orHW_SERVICE_UNAVAILABLE. Back off onHW_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_TIMEOUTneeds 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 anothersubmit()whileonAvailabilityChangereportstrue.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 callcheckout.recheck()once the runningsubmit()settles. Anothersubmit()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
customerEmailfrommount()tosubmit(). Callcheckout.submit({ customerEmail }). A cardsubmit()without an email now rejectsHW_INCOMPLETE, and TypeScript rejectscustomerEmailinmount()options. - Replace
hw.riskSessionKey(). The instance has onlymount()andchargeSavedMethod().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.
savePaymentMethodForFutureUseno 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
declinedinstead of rejectingHW_INCOMPLETE. Asubmit()email that differs from an earlier one is screened again instead of rejectingHW_UNSUPPORTED. Send the full billing address, includingpostalCode. 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:
| Checkout | 0.6.0 against the new release | 0.7.0 against an older release |
|---|---|---|
| Drop-in form that uses your Pay button | Fails 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 saving | Never 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 button | Keep 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.requiresSubmitis nowtrue. Show your Pay button, enable it fromonAvailabilityChange, and callcheckout.submit(). Without your own Pay button, the checkout takes no payment.mount()requirescustomerEmailand rejectsHW_UNSUPPORTEDwithout it.submit()must use the same email.submit()requires abillingAddresswithnameand a two-lettercountry. Alternatively, mount withbillingAddressForm: 'full'to collect the address inside the form.submit()resolvessucceededorpending, and errors carryrecovery; 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.