BouncerBouncerDocs

Devices

Configure physical and virtual devices for verification

Devices in Bouncer represent physical or virtual verification points such as kiosks, vending machines, point-of-sale systems, or tablets.

Overview

Devices provide a simplified verification flow:

  1. Device displays a QR code
  2. User scans the code with their phone
  3. User completes the verification on their phone
  4. Bouncer sends the result to your server by webhook, and your server tells the device

Creating a Device

Via Dashboard

  1. Log in to your Bouncer dashboard
  2. Navigate to Devices (labeled Web if your organization's Industry is not Vending)
  3. Click Create Device
  4. Configure the device settings:
    • Name: A descriptive name (e.g., "Store Front Kiosk #1")
    • Age Target: The minimum age to verify: 8, 13, 16, 18, 21, 25 or 30 (the form preselects 18)
    • Redirect URL: Where the user's phone goes after a successful verification
    • Webhook URL: Endpoint for verification events. It must be reachable from the internet (Bouncer does not deliver webhooks to hosts that resolve only to private addresses). Keep it to 255 characters or fewer
    • Send all events: On (default), every event of the session; off, only validation.completed
    • Force KYC Verification: Always require ID verification
    • Force Verification: Always on for devices and cannot be turned off: full verification every time, and the verification cookie never applies
    • Session Expiration: How long each session lasts, from 1 minute to 1 hour (default 5 minutes)

Device Code

Each device receives a unique 8-character code of uppercase letters and digits (e.g., K3M9X2QA) used for:

  • Direct verification URLs: /d/{deviceCode}
  • Dynamic session URLs via API

Device Verification Flows

Direct URL Flow

For static QR codes printed on devices:

https://app.bouncer.digital/d/K3M9X2QA

When scanned:

  1. Shows a consent screen, then a short face-scan tutorial
  2. When the user continues from the tutorial, creates a new session with device settings
  3. Redirects to verification page

Scanning this link does not cancel other sessions of the device, so several people can use the same printed QR at the same time.

Dynamic URL Flow

For devices that display dynamic QR codes:

# Generate a new session URL
curl -X POST https://app.bouncer.digital/api/v2/device/K3M9X2QA/session-url \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "qr_size": 400,
    "ttl": 300
  }'

Response (201 Created):

{
  "data": {
    "url": "https://app.bouncer.digital/s/7QX2M4KD",
    "qr": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIi...",
    "expires_at": 1793181720
  }
}

qr is an SVG image in a data URI that scales to the size you display it at, so qr_size does not fix its size. expires_at is in Unix seconds. See the API reference.

Display the QR code on your device screen. The short URL expires after the TTL. The first time it is opened, Bouncer cancels any open session of this device and creates a new session with the device settings, which lasts the device's Session Expiration. Until the TTL runs out, opening the same link again while that session is open leads to the same session, so remove the QR from the screen once it has been scanned.

Device Configuration

Settings

SettingDescriptionDefault
nameDevice identifierRequired
device_codeUnique code (auto-generated)Auto
age_targetAge to verify18
redirect_urlURL after a successful verificationOptional, but redirect_url or webhook_url is required
webhook_urlEvent notification URLOptional, but webhook_url or redirect_url is required
force_kycAlways require ID scanfalse
force_verificationRequire full verification every time; do not use cookie to skipAlways true
expiration_timeSession TTL (seconds)300
is_activeEnable/disable devicetrue

Example Device Flow

// Kiosk controller. Run it on your server, not on the kiosk:
// the API token must not be stored on the device.

class VerificationKiosk {
  constructor(apiToken, deviceCode, screen) {
    this.apiToken = apiToken;
    this.deviceCode = deviceCode;
    this.screen = screen;
    this.currentSession = null;
  }

  async displayNewQR() {
    const response = await fetch(
      `https://app.bouncer.digital/api/v2/device/${this.deviceCode}/session-url`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiToken}`,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          qr_size: 500,
          ttl: 300
        })
      }
    );

    if (response.status !== 201) {
      throw new Error(`Bouncer answered ${response.status}`);
    }

    const { data } = await response.json();

    // Display QR on screen
    this.showQRCode(data.qr);

    // Set up auto-refresh before expiration
    setTimeout(() => this.displayNewQR(), 280000); // Refresh at 4:40
  }

  showQRCode(dataUri) {
    // Send the SVG data URI to the kiosk screen, which shows it as the src of an <img>
    this.screen.show(dataUri);
  }
}

// Initialize kiosk
const kiosk = new VerificationKiosk('YOUR_API_TOKEN', 'K3M9X2QA', kioskScreen);
kiosk.displayNewQR();

Webhook Integration

Configure webhooks to receive real-time verification results. Every webhook carries a Signature header: the hex HMAC-SHA256 of the raw request body, keyed with your organization's API Secret. Verify it before you trust the body. See Webhooks.

// Express.js webhook handler
const crypto = require('node:crypto');
const API_SECRET = 'YOUR_API_SECRET'; // your organization's API Secret, used as a plain string

// Raw body: the signature is computed over the exact bytes Bouncer sends
app.post('/webhooks/device/:deviceCode', express.raw({ type: () => true }), (req, res) => {
  const { deviceCode } = req.params;
  const body = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);

  const expected = Buffer.from(
    crypto.createHmac('sha256', API_SECRET).update(body).digest('hex'), 'utf8');
  const received = Buffer.from(String(req.get('Signature') || ''), 'utf8');

  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, id, success } = JSON.parse(body.toString('utf8'));
  res.status(200).send('OK'); // answer within 3 seconds, then do the work

  if (event !== 'validation.completed') {
    return; // page visited, biometry or KYC started: progress only
  }

  if (success) {
    // User verified - unlock content, dispense product, etc.
    deviceController.onVerificationSuccess(deviceCode, id);
  } else {
    // Verification failed
    deviceController.onVerificationFailed(deviceCode, id);
  }
});

Treat every webhook as possibly the only delivery of that event, and reconcile by polling GET /api/v2/session/{id} (see Sessions). For a device session you learn the id from its webhooks, so keep Send all events on. Deduplicate by session id and event, and include success for validation.completed: a denial can be followed by an approval for the same id when the person then passes with an ID document. Only validation.completed with success: true is an approval.

Test Webhook, on the device page, resends the device's most recent real session as validation.completed, with its real id and success value. Nothing marks it as a test. If your endpoint dispenses or unlocks on success: true, make it ignore session ids it has already handled, or point the device at a test endpoint before you press it.

Device Branding

Devices inherit your organization's branding:

  • Logo: Displayed on verification page
  • Primary Color: UI accent color
  • Secondary Color: Background color

Customize branding in the user menu (your name at the bottom of the sidebar) > Organization > Branding.

Use Cases

Vending Machines

User approaches vending machine
    ↓
Selects age-restricted item
    ↓
QR code displayed on screen
    ↓
User scans, completes verification on phone
    ↓
Webhook notifies your server, which signals the machine
    ↓
Item dispensed

Point of Sale

Cashier initiates age check
    ↓
Customer tablet shows QR code
    ↓
Customer scans with their phone
    ↓
Verification completes
    ↓
POS system updated via webhook
    ↓
Transaction proceeds

Event Entry

Guest arrives at venue
    ↓
Scans QR code at gate
    ↓
Completes verification on phone
    ↓
Gate system receives webhook
    ↓
Access granted

Device Management

Activating/Deactivating

Devices can be temporarily disabled without deleting:

  1. Go to Devices
  2. Find the device
  3. Click Deactivate (or Activate to turn it back on), on the device page or in the row menu of the list

Inactive devices cannot create new sessions.

Monitoring

View device statistics:

  • Total Sessions, Successful and Failed (last 30 days)
  • Success Rate (last 30 days)
  • Recent Activity

Device Events

Each verification can send these webhook events:

  • validation.page_visited
  • validation.biometry_started
  • validation.kyc_started (if applicable)
  • validation.completed

validation.completed is always sent, for approvals and denials. The other events are sent only when Send all events is on, and validation.biometry_started and validation.kyc_started can arrive more than once. No event is sent when a session is created, expires or is cancelled.

Best Practices

  1. Use meaningful names: "Main Entrance Gate" not "Device 1"
  2. Set an appropriate Session Expiration: Longer for complex flows, shorter for quick checks
  3. Monitor webhook delivery: Ensure your endpoint is reliable, treat every webhook as possibly the only delivery, and reconcile by polling
  4. Implement fallbacks: Handle cases when Bouncer is unreachable, and handle a 429 with backoff
  5. Secure your devices: Keep API tokens off physical devices. Call the API from your server and send only the QR to the device: a token acts for your whole organization

On this page