SCREENGRID DEVELOPERS / V1

Build the connection.

Clear examples. Scoped keys. Rewards your backend can trust.

A working integration, step by step

ScreenGrid tracks referrals and purchase-based rewards. Use the portal without coding for in-store businesses, or connect your existing website/app backend. An NFC card opens an HTTPS page; it does not run an app or verify a purchase by itself.

  1. Open the Smart Cards portal and keep the environment on Test.
  2. Create and activate a program. Add a reward and generate a card.
  3. Activate the card in another browser profile. Verify a sample phone in test mode, then join through its referral on a second profile with a different sample phone.
  4. Create a test API key with only the permissions you need.
  5. Use the SDK to validate a code and record a first purchase. Watch balances update in the portal.
  6. Redeem a voucher. Then test a duplicate request and a refund before enabling live mode.
Test data and keys never award live points. Live mode needs platform SMS and mobile-line verification configured first.

Open the portal Download SDK package OpenAPI specification

Install the SDK

Requires Node.js 22 or newer on your backend. Download and extract the SDK folder into your project. This is a downloadable local package; it has not been published to npm.

npm install ./screengrid-smart-cards
# In your backend environment only:
# SCREENGRID_API_KEY=sg_test_your_generated_secret
const { ScreenGrid } = require('@screengrid/smart-cards');
const sg = new ScreenGrid({
  apiKey: process.env.SCREENGRID_API_KEY
});
const referral = await sg.validateReferral('RF-ABCDEF123456');
console.log(referral.valid, referral.referrer_verified);
Never put a secret API key in an iOS/Android app, React Native bundle, public website JavaScript, NFC tag or QR code. Apps call your backend. Your backend uses the secret-key SDK.

TypeScript declarations are included. The separate client.mjs helper has no secrets and can be bundled into browser or JavaScript-based mobile apps.

Authentication & permissions

Every developer endpoint starts with https://screengrid.co/api/smart-cards/v1. Send Authorization: Bearer YOUR_SECRET_KEY and JSON request bodies. A key is bound to one business program and one environment. You cannot select another business with a request parameter.

ScopeAllows
referrals:readValidate codes and check eligibility
members:readExchange a handoff and read member details
conversions:writeConfirm first purchases and reverse refunded purchases
rewards:readList rewards and inspect vouchers
rewards:redeemSpend points by redeeming a voucher

Keys are shown once and stored as a keyed hash. To rotate, create a replacement, deploy it to your backend, then revoke the previous key. Request logs show metadata only, not request bodies or secrets.

Valid code vs eligible customer

A valid code identifies an existing member in your program. It does not prove the person presenting it is new, verified or eligible for a discount. Validate the code first; after the friend joins through ScreenGrid and completes phone verification, check eligibility using their member ID.

const result = await sg.validateReferral('RF-ABCDEF123456');
// { valid: true, referrer_verified: true, environment: 'test', code: 'RF-ABCDEF123456' }

const eligibility = await sg.checkEligibility({
  member_id: verifiedMemberId,
  referral_code: 'RF-ABCDEF123456'
});
// { eligible: true, reason: null, minimum_cents: 500, currency: 'CAD' }

Ineligible reasons: invalid_code, verification_required, self_referral, referral_not_attached, or already_qualified. First-purchase eligibility remains used after a refund. A referral is attached when the member joins and cannot be replaced later.

Store the member ID on your own authenticated customer account after a secure handoff. Never trust a member ID typed into an untrusted request as proof of identity.

Connect your website or mobile app

In Program setup, enter an HTTPS destination URL owned by your business. The public Visit website button adds the referral code using your configured query parameter (default ref).

For a verified member, Continue securely creates a one-use ticket that expires in five minutes and places it in the destination URL fragment: #sg_claim=claim_.... Fragments are not sent in ordinary HTTP requests. Read the fragment on your page, remove it from browser history, and POST the ticket to your authenticated backend.

// Public browser or JavaScript mobile code — no secret API key.
import { handoffFromUrl, exchangeHandoff } from './client.mjs';
const ticket = handoffFromUrl(window.location.href);
if (ticket) {
  history.replaceState(null, '', location.pathname + location.search);
  const member = await exchangeHandoff(ticket, {
    endpoint: '/api/my-screengrid-membership',
    csrfToken: YOUR_APP_CSRF_TOKEN
  });
}
// Your backend. requireUser and validateCsrf are YOUR middleware.
app.post('/api/my-screengrid-membership', requireUser, validateCsrf, async (req, res, next) => {
  try {
    const member = await sg.resolveMember(req.body.ticket);
    // Save a UNIQUE binding between your user and member.id in your database.
    // Reject attempts to replace an existing membership or bind it to two users.
    await saveMembershipBinding(req.user.id, member.id);
    res.json({ id: member.id, name: member.name, balance: member.balance });
  } catch (error) { next(error); }
});

For native apps, configure iOS Universal Links or Android App Links on your own HTTPS domain, then parse the incoming URL and send the ticket to your backend. These require your domain association files and app configuration; ScreenGrid cannot configure them automatically for a third-party app.

If the app is not installed, show the app-store button and the referral code. A normal store redirect does not reliably preserve a referral through installation. Deferred deep-linking is a separate integration. Manual code entry remains the fallback; your app should send the customer to the ScreenGrid referral page to complete verified enrollment.

Native Swift/Kotlin apps can call their own backend using their normal HTTP client. The included SDK is Node.js plus a public JavaScript helper, not a native iOS or Android binary.

Confirm a purchase exactly once

Call this from your backend only after your payment processor confirms a completed purchase, or use the business portal for staff confirmation. Do not call it from a success-page click alone.

const conversion = await sg.confirmPurchase({
  member_id: savedScreenGridMemberId,
  external_id: 'order_1048',
  amount_cents: 1250
});

Amounts are integer cents in the program’s configured currency. ScreenGrid checks verification, minimum spend and previous qualification. It calculates both rewards from the program settings; callers cannot submit arbitrary point amounts.

external_id is your stable, unique order ID. Repeating the same request returns the existing conversion with replayed: true and does not award points again. Reusing an order ID with different customer or amount data fails. A different order ID cannot give the same member a second first-purchase bonus.

This version rewards the first qualifying purchase, not every purchase, and does not pay cash or create a discount inside another checkout automatically. To offer an immediate checkout discount, your checkout must enforce its own single-use discount and atomically reserve eligibility in your system; ScreenGrid’s eligibility response is an advisory check, not a purchase reservation.

// After your payment processor confirms a refund:
await sg.refundPurchase('order_1048');

A refund reverses the originally awarded points once. If points were already spent, the balance can go negative and future redemption is blocked until it recovers. A redeemed physical item is not automatically recoverable. Confirm purchases after the business’s chosen refund-risk period if necessary.

Reward goals & one-use vouchers

Businesses publish named rewards and point costs in the portal. Customers choose a reward as their meter’s target. Claiming creates a voucher valid for ten minutes; it does not spend points yet.

const { rewards } = await sg.listRewards();
const voucher = await sg.checkVoucher('RW-0123456789ABCDEF');
if (voucher.valid) {
  // Ask the employee to confirm before redeeming.
  const result = await sg.redeemVoucher(voucher.code);
  if (!result.replayed) {
    // Fulfil once; store result.id in your own fulfilment ledger.
  }
}

Redemption rechecks current balance, expiry and reward availability inside one database transaction. Repeated requests return the redeemed voucher with replayed: true. A business key cannot check or redeem another business’s vouchers.

Signed webhooks & reliable delivery

Add a public HTTPS endpoint in Developer. Copy the signing secret immediately and keep it on your backend. Saving a new endpoint rotates the secret. Supported events: conversion.confirmed, conversion.refunded, reward.redeemed, webhook.test.

const { verifyWebhook } = require('@screengrid/smart-cards');
// Register BEFORE app.use(express.json()) so the raw bytes are preserved.
app.post('/hooks/screengrid', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;
  try {
    event = verifyWebhook(req.body, req.get('ScreenGrid-Signature'),
      process.env.SCREENGRID_WEBHOOK_SECRET);
  } catch { return res.sendStatus(400); }
  // Transactionally store event.id in a table with a UNIQUE constraint.
  // Queue work in the same transaction; acknowledge duplicates with 200.
  await storeEventOnceAndQueueWork(event);
  res.sendStatus(200);
});

Header format: t=UNIX_SECONDS,v1=HEX_HMAC. The signed input is the timestamp, a period, and the exact raw JSON body. HMAC-SHA256 uses the webhook secret. The SDK rejects signatures older than five minutes; keep your server clock accurate.

Events are stored with the purchase/redemption transaction. The worker runs every 30 seconds and retries failed deliveries up to eight attempts with exponential backoff. Delivery is at least once and ordering is not guaranteed. Store processed event IDs; do not issue a reward twice because the same webhook arrives twice. Use Developer’s delivery list and Retry button after fixing a failed endpoint.

Webhook endpoints must resolve to public IPv4 addresses on HTTPS port 443. Redirects and private/internal network destinations are rejected. Receivers should acknowledge promptly and process work asynchronously.

Endpoint reference

Download the OpenAPI file for request fields, schemas and authentication. All keys are scoped to their own environment.

Show code in your language

Try a test request

This same-origin console accepts test keys only. The key stays in this page’s input and is not saved to browser storage. For production, use your backend.

Your response will appear here.

Errors, limits & troubleshooting

Errors return JSON: {"error":{"code":"…","message":"…"},"request_id":"req_…"}. Keep the request ID when asking for help. Routes enforce business isolation and permissions before reading data.

StatusWhat to do
400Check fields, minimum purchase, verification, and current reward eligibility. Business-rule errors currently use invalid_request or database_error.
401Replace an invalid/revoked key or sign in again.
403Add the required scope to a new API key.
404Check the code/member ID and test/live environment; the program may be paused.
409Duplicate phone/account or expired/consumed handoff; inspect the error message.
422Phone is invalid, VoIP, or not confidently classified as mobile.
429Pause and retry later. API limits: 120 requests/minute per key and per source IP. PIN: five attempts per code per 15 minutes.
503Platform configuration or phone lookup is unavailable. Rewards remain locked; do not bypass verification.

After a purchase timeout, retry the same external order ID. Do not generate a new ID. After a handoff timeout, obtain a new handoff if it was already consumed. After a redemption timeout, check the voucher before handing out an item.

Google linking is optional and does not bypass phone verification. Reinstalling an app, changing cards or changing referral codes does not create a new welcome bonus for an existing verified phone in the same business.