High Wire Payments

Device screening for Direct API charges

Get the device-session key and browser context for a charge your backend sends through the Direct API.

When you need the collector

mount() runs its own device session and attaches the key to the payments it creates. A checkout that pays only through the Payment SDK needs nothing on this page.

When your backend charges through the Direct API instead, with auth or auth-and-capture, the browser has to supply the screening context itself. The collector does that: HighWireCollect from the @highwirepayments/payment-sdk/collect entry. It loads no checkout code. The Payment SDK instance does not return the key.

Load the collector

Load it from the CDN with its integrity hash:

<script src="https://cdn.highwirepayments.com/collect/0.7.0/highwire-collect.js"
        integrity="sha384-TZ+jUu/98zNrFmHe2gs1NDAf2TZVnqI3f9Z3UxvphBa/5DnsPdMy0Cwp8kZ6ygRz"
        crossorigin="anonymous"></script>

The script registers window.HighWireCollect. A published version is never replaced, so its hash stays valid for that URL. Ask HighWire for the hash of each version.

Or install the npm package and import the entry. Bundled imports do not use the hash:

npm install @highwirepayments/payment-sdk@0.7.0
import { HighWireCollect } from '@highwirepayments/payment-sdk/collect';

The collect entry is declared in the package's exports map, so your bundler must support it: webpack 5, Vite, Rollup, esbuild, and TypeScript's bundler, node16 and nodenext resolution all do; webpack 4 does not.

If your page sets a Content Security Policy, allow:

  • script-src: https://cdn.highwirepayments.com/collect/ when you load it from the CDN, plus the screening script origins HighWire supplies for your account. The scripts the collector injects carry no nonce, so a nonce-based policy must list those hosts.
  • connect-src: your apiBase origin, for the session and the browser's IP; the fallback IP lookups https://api.ipify.org and https://ifconfig.co; and the screening connect origins HighWire supplies for your account.

A blocked request makes the affected method resolve null.

Create it early

Create one collector in browser code as soon as the checkout page loads, with your publishable key and the same apiBase you pass to HighWire.init():

const collector = HighWireCollect({
  publishableKey: 'REPLACE_WITH_YOUR_TEST_PUBLISHABLE_KEY',
  apiBase: 'https://api-payments-staging.highwirepayments.com',
});

Creating it with publishableKey starts the device session at once, so screening signals build up while the shopper fills in the form. A malformed key throws. Without apiBase, the collector starts the session on the production gateway, whatever environment your checkout uses. Without publishableKey, it starts no session and riskSessionKey() always resolves null.

A page that runs both the Payment SDK and the collector starts two device sessions, one each. The checkout's own payments use its session; your Direct API charges use the collector's. On some accounts the second session reports under the first one's key, so both can return the same key. Forward whatever the collector returns.

Send the key and device data with the charge

In your submit handler, read both values and send them to your backend with the order:

async function deviceDataOrNull() {
  try {
    return await collector.collect();
  } catch {
    return null;
  }
}

async function submitOrder(order) {
  const deviceData = await deviceDataOrNull();
  const riskSessionKey = await collector.riskSessionKey();

  return fetch('/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      ...order,
      ...(deviceData && { deviceData }),
      ...(riskSessionKey && { riskSessionKey }),
    }),
  });
}

Your backend forwards both unchanged on the authorization or authorization-and-capture request: riskSessionKey as a top-level field, a sibling of deviceData and never inside it. Omit a field whose value is null. See the request body and device data rules.

Create the collector with publishableKey and call collect() only in the shopper's browser. The constructor reads document, so it throws during server rendering, and a server runtime can make collect() return valid-looking server values instead of failing, which attaches the wrong context to the charge.

riskSessionKey()

riskSessionKey(): Promise<string | null>

It never rejects, and it waits at most 2 seconds:

  • It resolves to the key once the session is live, usually a few seconds after the page loads. On a page that has been open for more than a moment, awaiting it in the submit handler costs nothing.
  • It resolves to null when there is no screening: the collector was created without publishableKey, the session request failed or was refused, screening is off for your account, or the screening script was blocked.
  • It also resolves to null when the session has not gone live within 2 seconds of the call. The session keeps loading, so a later call can still return the key.
  • On some accounts it resolves a key even when the screening script is blocked or slow. Forward any key it returns.

On null, omit riskSessionKey: the charge proceeds and is screened without device signals. HighWire ignores a key it did not mint for your account, or minted more than an hour ago, so a wrong value weakens the risk decision rather than failing the call. Treat the key as optional context, not authentication or proof of payment. Do not log it or put it in URLs.

Troubleshooting

  • HighWireCollect is not defined or is not a function: the code ran before the CDN script loaded, or the script URL or script-src blocked it. Create the collector after the script has loaded, or import it from npm.
  • The browser reports an integrity mismatch: the hash belongs to another version, or the file was changed after it was hashed. Use the hash for the exact version in the URL, and serve the file unchanged.
  • riskSessionKey() keeps resolving null: check that the collector was created with your publishable key and the same apiBase as HighWire.init(). In the Network panel, check POST /v1/sdk/payments/session: a 401 means the key is unknown, often a test key sent to the production gateway because apiBase is missing; a 403 means your checkout origin is not registered with HighWire or your account is disabled or restricted; a 409 with HW_NO_ACTIVE_PROVIDER_ACCOUNT means your account takes no new payments. Check the console for script-src or connect-src violations against the screening origins HighWire supplies for your account. Whatever the cause, omit the field and continue the charge.
  • collect() resolves null: the browser's IP or user agent could not be read, often because connect-src blocks the lookup. Omit deviceData and continue the charge.

On this page