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:

SUBMIT HANDLER PATTERN
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:

NO TRY/CATCH NEEDED
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:

  1. Your backend receives a decline indicating VPN usage
  2. You display a message asking the shopper to disable their VPN
  3. The shopper disables their VPN
  4. They click "Try again"
  5. Your submit handler calls recordSonarEvent(), capturing a fresh event from the clean network
  6. The retry succeeds
VPN RECOVERY FLOW
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:

CONCURRENT CALLS COALESCE
// 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.

Frame Assistant

Ask anything about Frame's APIs and products