Integration
Collect, observe, and forward deviceData through a HighWire direct-API checkout.
Integration flow
A complete integration keeps the existing payment path and adds deviceData at each handoff:
- The checkout initializes
highwire.jsand opts supported fields into paste-event observation. - On form submission, the checkout awaits collection and sends the result to the merchant backend with the existing payment fields.
- The merchant backend forwards the collected object unchanged as
deviceDataon its HighWire direct-API request.
The browser and backend examples below show only the highwire.js additions to an already-valid merchant payment request. Your existing payment fields and route plumbing remain governed by your HighWire gateway integration.
Initialize highwire.js
For a self-hosted UMD build, load the script and create an instance in browser code:
<script
src="/assets/highwire-v<version>.js"
integrity="sha384-REPLACE_WITH_THE_HASH_HIGHWIRE_SENT_YOU"
crossorigin="anonymous"
defer
></script>
<script src="/assets/checkout-highwire.js" defer></script>Create the instance in the same-origin /assets/checkout-highwire.js companion file used by the checkout:
const hw = HighWire();Keeping the initializer in an external file allows a strict script-src 'self' policy without unsafe-inline. With the ESM build, import HighWire from your vendored file and create the same instance in the checkout's client-side module. See Getting started for build selection and placement.
Observe checkout fields
Call observeField() after the checkout inputs exist in the DOM. Opt in only the fields your form has:
hw.observeField(document.querySelector('#card-number'), 'number');
hw.observeField(document.querySelector('#card-cvc'), 'cvc');
hw.observeField(document.querySelector('#card-exp'), 'exp');
hw.observeField(document.querySelector('#billing-zip'), 'zip');Observation records paste events, not field contents. The library never reads element.value. See the API reference for the supported calls and field names.
Collect on submission
Pass the payment request your checkout already produces into the submit handler. Await collection and add deviceData only when collection succeeds:
async function collectDeviceDataOrNull() {
try {
return await hw.collect();
} catch {
return null;
}
}
async function submitCheckout(existingPaymentRequest) {
const deviceData = await collectDeviceDataOrNull();
const response = await fetch('/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...existingPaymentRequest,
...(deviceData ? { deviceData } : {}),
}),
});
if (!response.ok) {
throw new Error('Payment request failed');
}
return response;
}existingPaymentRequest represents the complete request your checkout already sends to your backend. highwire.js supplies only deviceData; it does not read, construct, validate, or submit the merchant-owned payment fields.
Normal provider or required-context failures resolve to null. A missing or broken platform primitive can instead reject the promise. The helper treats either outcome as unavailable context, so the checkout can continue without deviceData. Do not construct a substitute object.
Send deviceData to your backend
Send the collected object to your own checkout endpoint as a top-level deviceData field alongside the payment request you already produce. Preserve it as returned rather than selecting, renaming, or supplementing its properties. For details about the collected object, see Device data.
The conditional spread in the browser example omits the field when collection returns null:
body: JSON.stringify({
...existingPaymentRequest,
...(deviceData ? { deviceData } : {}),
})Forward deviceData to HighWire
In your existing backend checkout handler, pass the object through unchanged on the direct-API request:
const paymentAttempt = await loadExistingPaymentAttempt(request);
const gatewayResponse = await fetch(
'https://api-payments.highwirepayments.com/v1/payments/auth-and-capture',
{
method: 'POST',
headers: {
Authorization: `Basic ${merchantBasicAuth}`,
'Content-Type': 'application/json',
'Idempotency-Key': paymentAttempt.idempotencyKey,
},
body: paymentAttempt.serializedGatewayRequestBody,
},
);loadExistingPaymentAttempt(request) represents the merchant's existing server-side attempt store. Before the first gateway call, conditionally add the successful deviceData object to existingPaymentRequest, serialize that complete gateway request once, and persist both the exact JSON string and one idempotency key. For every retry of that logical attempt, reuse the same key and the same request body by sending paymentAttempt.serializedGatewayRequestBody with paymentAttempt.idempotencyKey; do not recollect browser context or rebuild or reserialize the body. A new logical payment attempt receives a new key and body record. merchantBasicAuth, payment-attempt state, and gateway credentials must remain server-side.
Handle unavailable device data
Normal provider or required-context failures resolve to null. A missing or broken platform primitive can instead reject the promise. The helper treats either outcome as unavailable context, so the checkout can continue without deviceData. Do not construct a substitute object.
If collection is unexpectedly unavailable, check script loading, SRI, CSP, and IP-provider access in Troubleshooting.
Clean up in single-page applications
observeField() returns a cleanup function. Keep each function and call it when the checkout form unmounts so that old elements no longer have listeners:
const stopObserving = [
hw.observeField(cardNumberInput, 'number'),
hw.observeField(cardCvcInput, 'cvc'),
hw.observeField(cardExpirationInput, 'exp'),
hw.observeField(billingZipInput, 'zip'),
];
function unmountCheckout() {
stopObserving.forEach((stop) => stop());
}Cleanup prevents future events from the detached elements. It does not clear names already captured by that instance; create a new HighWire() instance when a new checkout attempt requires a fresh observation history.
Initialize and observe again when a new form instance mounts. See the React and Next.js guide for component lifecycle examples.