Snap JavaScript SDK (v2) — Quick Start Guide (Expanded Checkout + Get Approved)

Public edition • v2 • Stable

Overview

Snap’s JavaScript SDK adds a secure "Pay with Snap" checkout option. The SDK opens a secure application window and returns results through callbacks (for example, an applicationId when approved). This guide includes practical samples for handling checkout approval and the "Get Approved" marketing treatment flow.

How it works (brief)
  1. Your server exchanges Client ID/Secret for a short-lived bearer token (~24 hours) and returns it to your frontend via a secure endpoint.
  2. Your frontend loads the Snap SDK v2, calls snap.init(token), then renders the checkout mark and checkout button.
  3. On button click, call actions.launchCheckout(transaction). Handle events like onApproved, onDenied, onCanceled, and onError.

Step 1 — Verify requirements

  • Snap Developer credentials: Client ID and Client Secret.
  • A secure server to keep secrets and request tokens.
  • Frontend where the SDK will be loaded and the button will be shown.
  • Serve pages over https in production. Localhost is acceptable for development.

Step 2 — Choose environment

Sandbox (testing)
audience: https://api-release.snapfinance.com/platform/v1
Production (live)
audience: https://api.snapfinance.com/platform/v1

Step 3 — Get an access token

Request a server token and cache it. The token typically expires in ~24 hours. Rotate slightly before expiry.

3.1 Token request (example cURL)

curl --location 'https://auth-sandbox.snapfinance.com/oauth/token' \
--header 'Content-Type: application/json' \
--data '{
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "audience": "https://api-release.snapfinance.com/platform/v1",
  "grant_type": "client_credentials"
}'

3.2 Server endpoint (Node/Express example)

// server.js (minimal)
const express = require('express');
const fetch = require('node-fetch');
const app = express();

app.get('/api/snap/token', async (req, res) => {
  const resp = await fetch('https://auth-sandbox.snapfinance.com/oauth/token', {
    method:'POST',
    headers:{ 'Content-Type':'application/json' },
    body: JSON.stringify({
      client_id: process.env.SNAP_CLIENT_ID,
      client_secret: process.env.SNAP_CLIENT_SECRET,
      audience: 'https://api-release.snapfinance.com/platform/v1',
      grant_type: 'client_credentials'
    })
  });
  const json = await resp.json();
  res.json({ access_token: json.access_token, expires_in: json.expires_in });
});

app.listen(3000);

Step 4 — Add the Web SDK (v2)

4.1 Include script

<!-- Sandbox -->
<script src="https://js.snapfinance.com/sandbox/v2/snap-sdk.js"></script>

<!-- Production -->
<!-- <script src="https://js.snapfinance.com/v2/snap-sdk.js"></script> -->

4.2 Initialize (frontend)

<script>
  // Fetch token from your server, then:
  const { access_token } = await fetch('/api/snap/token').then(r => r.json());
  snap.init(access_token);
</script>

Step 5 — Build the transaction object

Money fields must be strings with two decimals and quantity must be an integer. Use a unique orderId.

// Build transaction (client-side)
function toMoney(v){ return Number(v).toFixed(2); }
function buildTransaction(items, tax, shipping, discounts){
  const subtotal = items.reduce((s,i)=> s + Number(i.price)*Number(i.quantity), 0);
  const total = subtotal + Number(tax) + Number(shipping) - Number(discounts);
  return {
    customerInformation: {
      email: "",
      firstName: "",
      lastName: "",
      billingAddress: {
        streetAddress: "",
        city: "",
        state: "",
        country: "",
        postalCode: "",
        unit: ""
      }
    },
    cartInformation: {
      currencyCode: "USD",
      taxAmount: toMoney(tax),
      shippingAmount: toMoney(shipping),
      discountAmount: toMoney(discounts),
      totalAmount: toMoney(total),
      orderId: "ORD-" + Date.now(),
      items: items.map(i => ({
        price: toMoney(i.price),
        itemId: i.itemId,
        description: i.description,
        sku: i.sku,
        quantity: Number(i.quantity),
        leasable: Boolean(i.leasable)
      })),
      shippingAddress: { /* same as billing or separate */ }
    }
  };
}

Step 6 — Render mark and button (checkout)

Render mark and button after snap.init(token). Do not render before initialization.

<div id="snap-checkout-mark"></div>
<div id="snap-checkout-button"></div>
<input id="applicationId" placeholder="Application ID will appear here" readonly />

6.1 Ensure initialization

function ensureInit(token){
  if(!window.snap) throw new Error('Snap SDK missing');
  snap.init(token);
}

6.1 Checkout — robust sample

This sample shows a safe click handler, retry on "not initialized", and approved handling that persists to your server for verification.

<script>
const appIdEl = document.getElementById('applicationId');

function renderMark(){
  snap.checkoutMark({ style:{ color:'dark', height:40 } }).render('snap-checkout-mark');
}

function renderButton(items, tax, shipping, discounts){
  const txBuilder = () => buildTransaction(items, tax, shipping, discounts);

  const onClick = (data, actions) => {
    const tx = txBuilder();
    return actions.launchCheckout(tx).catch(err => {
      if (/not initialized/i.test((err && err.message) || '')) {
        try { snap.init(window.__SNAP_TOKEN__ || ''); } catch(_) {}
        return actions.launchCheckout(tx);
      }
      throw err;
    });
  };

  snap.checkoutButton({
    style:{ color:'dark', shape:'pill', height:42 },
    onClick: onClick,
    onApproved: async (data) => {
      console.log('Approved callback', data);
      if (data && data.applicationId) {
        appIdEl.value = data.applicationId;
        await fetch('/api/snap/verify-approval', {
          method:'POST',
          headers:{ 'Content-Type':'application/json' },
          body: JSON.stringify({ applicationId: data.applicationId, orderId: data.orderId })
        });
      }
    },
    onDenied: (data) => console.warn('Denied', data),
    onCanceled: (data) => console.info('Canceled', data),
    onNotification: (data) => console.info('Notification', data),
    onError: (data) => console.error('Error', data)
  }).render('snap-checkout-button');
}
</script>

6.2 Server verify approved application

Once approved, verify the applicationId server-side via Snap APIs or your internal reconciliation logic.

// Example express endpoint to reconcile an applicationId
app.post('/api/snap/verify-approval', express.json(), async (req, res) => {
  const { applicationId, orderId } = req.body;
  // Protect and make idempotent. Persist and reconcile as required.
  await saveApprovalToDb({ applicationId, orderId, receivedAt: Date.now() });
  res.sendStatus(200);
});
Best practice

Store applicationId with your orderId and reconcile server-side for reporting and settlement.

6.3 SDK events (detailed)

Use these callbacks to manage UI states, capture outcomes, and reconcile approvals.

EventWhen it firesWhat it meansRecommended handling
onClick User clicks the Snap button SDK provides an actions object used to start checkout Build the final transaction, prevent duplicate clicks, call actions.launchCheckout(tx)
onApproved Customer is approved Approval complete, applicationId is available Persist applicationId, then verify and reconcile on your server
onDenied Customer is denied Underwriting did not approve the application Show a friendly message, offer other payment methods, log analytics
onCanceled User closes or cancels Flow ended without a decision Return user to checkout, allow retry, do not submit order
onNotification Informational status updates Intermediate events (non-final) Use for logging or lightweight UI hints, not final outcomes
onError Validation, SDK, or network errors Checkout could not complete Show error UI, log details, surface validationErrors if present
Common pitfall

If you see session not initialized, ensure you call snap.init(token) before rendering or launching checkout, and use the correct token for the selected environment.

Step 7 — Get Approved treatment

The "Get Approved" flow uses snap.launch() with a marketing treatment payload. This is separate from checkout and is commonly used for promotional or pre-approval entry points.

Important

If a specific SDK build exposes preApprovalButton and you must avoid it, install a guard after SDK load (shown below).

7.1 Get Approved — samples

7.1.1 Simple launch (promise)

async function launchGetApproved(payload){
  if (!window.snap || typeof window.snap.launch !== 'function') {
    throw new Error('snap.launch missing');
  }
  const res = window.snap.launch(payload);
  return (res && typeof res.then === 'function') ? await res : res;
}

7.1.2 Full example with guard + storage + server verify

async function doGetApprovedFlow(){
  const { access_token } = await fetch('/api/snap/token').then(r => r.json());
  snap.init(access_token);
  window.__SNAP_TOKEN__ = access_token;

  // Guard unsupported API usage (optional)
  if (window.snap && typeof window.snap.preApprovalButton !== 'function') {
    window.snap.preApprovalButton = function(){ throw new Error('preApprovalButton blocked'); };
  }

  const payload = {
    token: access_token,
    marketing: { treatment: 'PRE_APPROVAL' },
    data: { /* optional */ }
  };

  const result = await launchGetApproved(payload);
  if (result && result.applicationId) {
    sessionStorage.setItem('approvedApplicationId', result.applicationId);
    await fetch('/api/snap/verify-approval', {
      method:'POST',
      headers:{ 'Content-Type':'application/json' },
      body: JSON.stringify({ applicationId: result.applicationId })
    });
  }
}

If available, you may also call snap.sendMarketingMetadata(...) as a non-blocking step. Always catch errors and continue the flow.

Step 8 — Validate totals

function sumItems(arr){ return arr.reduce((s,i)=> s + Number(i.price)*Number(i.quantity), 0); }
function validateTransaction(tx){
  const ci = tx && tx.cartInformation; if (!ci) throw new Error('cartInformation missing');
  if (!ci.orderId) throw new Error('orderId is required');
  const computed = sumItems(ci.items) + Number(ci.taxAmount) + Number(ci.shippingAmount) - Number(ci.discountAmount);
  if (Math.abs(computed - Number(ci.totalAmount)) > 0.01) throw new Error('totalAmount mismatch');
}

Step 9 — Troubleshoot

SymptomCauseFix
Money fields rejectedNumbers used instead of stringsSend strings with two decimals, for example "12.34"
Quantity rejectedQuantity sent as stringSend integer
"Session not initialized"Render or launch called before snap.init()Initialize then render; add a single retry on init failures
Get Approved errors immediatelyMissing token or unsupported SDK buildVerify token environment, confirm snap.launch exists, log Object.keys(window.snap)

Step 10 — Security best practices

  • Keep client_secret on the server. Never embed it in frontend code.
  • Use HTTPS for production token requests and API calls.
  • Cache tokens server-side and refresh before expiry.
  • Verify approvals server-side using applicationId.

Step 11 — Go-live checklist

  • Server endpoint issues token and caches it.
  • Frontend loads the correct SDK script for environment and calls snap.init(token).
  • Transaction object validates locally before checkout.
  • Checkout calls actions.launchCheckout(transaction) and handles events.
  • Approvals are verified server-side using applicationId.

Snap Checkout Demo HTML ↗