Sonar events
Frame collects device intelligence — called a sonar event — when the SDK initializes. The sonar event feeds into Frame's fraud scoring and is attached to the browser session that accompanies every payment or transfer.
For most checkouts, the initial sonar event captured at page load is sufficient. But when a shopper changes their network (disables a VPN, switches Wi-Fi) mid-session, that initial event no longer reflects the device's current state. recordSonarEvent() lets you capture a fresh event before submitting a payment.
The always-call convention
Call recordSonarEvent() unconditionally at the top of your checkout submit handler and await it before sending the request to your backend:
async function handleCheckoutSubmit() {
await frame.recordSonarEvent();
await submitPaymentToBackend();
}
The SDK enforces a freshness threshold internally — when the current sonar event is less than 30 seconds old, the call returns immediately without re-collecting. You don't need to track freshness yourself; always call it and let the SDK decide.
Benefits of the always-call pattern:
- Consistent integration — the same two lines in every submit handler, no conditional logic.
- Future-proof — if Frame's freshness requirements change, your integration already calls the method.
- VPN recovery — when a shopper disables their VPN after a rejection, the next submit captures a clean event (see below).
The never-throws contract
recordSonarEvent() never rejects. If device collection fails (Fingerprint outage, network error, session update failure), the SDK reports the failure to Frame telemetry and resolves the promise normally. Your checkout flow is never blocked by a fraud-signal outage.
This means you can await it without a try/catch:
await frame.recordSonarEvent(); // never throws
await submitPaymentToBackend();
VPN recovery loop
When Frame rejects a payment due to a VPN or proxy, the typical recovery flow is:
- Your backend receives a decline indicating VPN usage
- You display a message asking the shopper to disable their VPN
- The shopper disables their VPN
- They click "Try again"
- Your submit handler calls
recordSonarEvent(), capturing a fresh event from the clean network - The retry succeeds
async function handleCheckoutSubmit() {
await frame.recordSonarEvent();
const result = await submitPaymentToBackend();
if (result.error?.code === "vpn_detected") {
showMessage("Please disable your VPN and try again.");
return;
}
// success path
}
The shopper doesn't reload the page — the recordSonarEvent() call on retry captures their new, non-VPN network state. Two attempts, no page reload required.
Concurrency
Concurrent calls to recordSonarEvent() share a single refresh. If your UI allows double-clicks or you call it from multiple places, only one device collection runs:
// Both resolve after the same single refresh
await Promise.all([
frame.recordSonarEvent(),
frame.recordSonarEvent(),
]);
Sealed vs. legacy environments
recordSonarEvent() works in both Frame's sealed and legacy fraud environments. On the sealed path, the refresh guarantees the backend receives a fresh, tamper-evident event. On the legacy path, the refresh is best-effort — it updates the session but without sealed attestation.
New integrations on sealed intake get the full benefit. Existing integrations on the legacy path still benefit from the refresh for VPN recovery, but the fraud signal quality depends on the environment configuration.