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.0import { 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: yourapiBaseorigin, for the session and the browser's IP; the fallback IP lookupshttps://api.ipify.organdhttps://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
nullwhen there is no screening: the collector was created withoutpublishableKey, the session request failed or was refused, screening is off for your account, or the screening script was blocked. - It also resolves to
nullwhen 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 definedoris not a function: the code ran before the CDN script loaded, or the script URL orscript-srcblocked 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 resolvingnull: check that the collector was created with your publishable key and the sameapiBaseasHighWire.init(). In the Network panel, checkPOST /v1/sdk/payments/session: a401means the key is unknown, often a test key sent to the production gateway becauseapiBaseis missing; a403means your checkout origin is not registered with HighWire or your account is disabled or restricted; a409withHW_NO_ACTIVE_PROVIDER_ACCOUNTmeans your account takes no new payments. Check the console forscript-srcorconnect-srcviolations against the screening origins HighWire supplies for your account. Whatever the cause, omit the field and continue the charge.collect()resolvesnull: the browser's IP or user agent could not be read, often becauseconnect-srcblocks the lookup. OmitdeviceDataand continue the charge.