FloPayFloPay
Guides

Checkout Analytics

Capture how each checkout was rendered to the buyer — which AVS fields were shown, which surface and layout were used — so you can correlate UX choices with conversion, auth, and chargeback rates.

Checkout Analytics

Every checkout session records four metadata fields that describe how the checkout was presented to the buyer. These fields live on the checkout_session row in the billing API and let you correlate UX choices (AVS configuration, layout, surface) with downstream outcomes — auth rate, conversion, chargeback rate.

What Gets Recorded

ColumnTypeRecords
avs_checkboolean (default false)Were any AVS fields shown to the buyer?
avs_configjsonb (nullable)The resolved exposure — which fields were actually visible for this buyer's country (booleans only, not the raw rules)
checkout_typestring (nullable)'standard_checkout' (hosted /secure page) or 'embedded_checkout' (FloPayCheckout inline on a partner site)
checkout_layoutstring (nullable)'default_layout', 'buttons_layout', or 'custom_layout'

All four columns are nullable (or default false). Older sessions and non-Stripe flows leave them empty — your existing analytics queries continue to work unchanged.

avs_config — resolved exposure, not raw rules

avs_config records the fields the buyer actually saw, not the rule that decided it. If the SDK was configured with enableAVS={{ address_line_1: ['US','CA'] }} and the buyer is in Germany, avs_config.address_line_1 is false — to that buyer the field didn't exist.

This makes downstream queries simple:

-- US sessions where the buyer entered a street address
SELECT COUNT(*)
FROM checkout_session
WHERE user_address_country = 'US'
  AND avs_config->>'address_line_1' = 'true';

Example row:

{
  "country": true,
  "postal_code": true,
  "address_line_1": true,
  "address_line_2": false,
  "city": false,
  "state": false
}

How the Fields Get Populated

The SDK populates all four fields automatically. There are two write paths:

  1. At session creation — when you use FloPayCheckout's createSession prop or createCheckoutSession, the analytics fields are sent in the create body alongside items, account data, etc.
  2. At process time — when the buyer submits payment, the /process request also carries the analytics fields. The backend writes them with an atomic UPDATE … WHERE column IS NULL (and WHERE avs_check = false for the boolean), so concurrent process calls cannot race or overwrite each other.

If both paths set a value, the first writer wins — the create-time value is preserved because the conditional update only runs when the column is still null (or false for avs_check).

Independent Session Creation

When a partner creates the checkout session server-to-server (their backend calls POST /v1/checkouts/sessions and only hands the resulting sessionId to the SDK), the analytics fields can come from one of two places:

Option A — Let the SDK populate at process time (zero changes)

Don't include analytics fields in the create body. The SDK still sends them on /process, and the conditional update writes them to the row before payment is finalized.

This is the recommended default for most consumers. As long as the partner is on @flopay/react@0.5.0 or later, the analytics columns will be populated for every completed payment without any backend changes.

Option B — Set them at session creation

Pass the four fields in the create body to populate analytics on every session, including abandoned ones (where /process is never called).

await fetch(`${BILLING_API_URL}/v1/checkouts/sessions`, {
  method: 'POST',
  headers: { /* … */ },
  body: JSON.stringify({
    clientId: 'your-client-id',
    items: [{ providerItemId: 'product-1', totalAmount: 29.99, currency: 'USD' }],
    account: { userId: 'user_1', email: 'user@example.com', country: 'US' },
    successUrl: '/success',
    cancelUrl: '/cancel',
 
    // ── Analytics ──
    avsCheck: true,
    avsConfig: {
      country:        true,
      postal_code:    true,
      address_line_1: true,   // resolved against the buyer's country
      address_line_2: false,
      city:           false,
      state:          false,
    },
    checkoutType:   'embedded_checkout',
    checkoutLayout: 'buttons_layout',
  }),
});

The DTO validates these fields:

  • checkoutType must be 'standard_checkout' or 'embedded_checkout'
  • checkoutLayout must be 'default_layout', 'buttons_layout', or 'custom_layout'
  • avsConfig must be an object
  • avsCheck must be a boolean

Invalid values fail fast with a 400.

Mixing the two is safe. If the create body sets a value, the SDK's later /process write becomes a no-op — setAnalyticsIfUnset only writes when the column is still null. Partners can opt in to early population without breaking SDK-driven population.

What you should NOT do

  • Don't send the raw enableAVS config. The SDK's enableAVS prop accepts per-country rules like address_line_1: ['US','CA']. Those rules are authoring state — avs_config records exposure. If you set avs_config server-side, resolve the rules against the buyer's country yourself first.
  • Don't send avsCheck: true while sending an avsConfig where every field is false. These will look inconsistent in queries.
  • Don't backfill on completed sessions. setAnalyticsIfUnset is the only safe writer; manual UPDATEs can overwrite values that downstream consumers have already read.

Suggested Analyses

AVS impact on auth rate

SELECT
  user_address_country AS country,
  avs_check,
  COUNT(*) AS sessions,
  AVG(CASE WHEN status = 'completed' THEN 1.0 ELSE 0 END) AS auth_rate
FROM checkout_session
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY country, avs_check
ORDER BY country, avs_check;

Marginal value of street address (US only)

SELECT
  avs_config->>'address_line_1' AS line1_shown,
  COUNT(*) AS sessions,
  AVG(CASE WHEN status = 'completed' THEN 1.0 ELSE 0 END) AS auth_rate
FROM checkout_session
WHERE user_address_country = 'US'
  AND avs_check = true
  AND created_at > NOW() - INTERVAL '30 days'
GROUP BY line1_shown;

Layout conversion

SELECT checkout_layout, COUNT(*), AVG(...)
FROM checkout_session
WHERE checkout_layout IS NOT NULL
GROUP BY checkout_layout;

Standard vs embedded surface

SELECT checkout_type, COUNT(*), AVG(...)
FROM checkout_session
WHERE checkout_type IS NOT NULL
GROUP BY checkout_type;

SDK Reference

The four analytics fields are part of the public typings on @flopay/shared:

Backwards Compatibility

  • All four columns are nullable (or default false). Existing rows are unaffected.
  • Older SDK clients (pre-0.5.0) send neither path — those rows just stay NULL.
  • Non-Stripe flows (Recurly, Chargebee) don't populate these fields today.
  • Partner integrations that don't upgrade the SDK can still populate analytics manually via Option B.