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:
- Device displays a QR code
- User scans the code with their phone
- User completes the verification on their phone
- Bouncer sends the result to your server by webhook, and your server tells the device
Creating a Device
Via Dashboard
- Log in to your Bouncer dashboard
- Navigate to Devices (labeled Web if your organization's Industry is not Vending)
- Click Create Device
- 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/K3M9X2QAWhen scanned:
- Shows a consent screen, then a short face-scan tutorial
- When the user continues from the tutorial, creates a new session with device settings
- 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
| Setting | Description | Default |
|---|---|---|
name | Device identifier | Required |
device_code | Unique code (auto-generated) | Auto |
age_target | Age to verify | 18 |
redirect_url | URL after a successful verification | Optional, but redirect_url or webhook_url is required |
webhook_url | Event notification URL | Optional, but webhook_url or redirect_url is required |
force_kyc | Always require ID scan | false |
force_verification | Require full verification every time; do not use cookie to skip | Always true |
expiration_time | Session TTL (seconds) | 300 |
is_active | Enable/disable device | true |
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 dispensedPoint of Sale
Cashier initiates age check
↓
Customer tablet shows QR code
↓
Customer scans with their phone
↓
Verification completes
↓
POS system updated via webhook
↓
Transaction proceedsEvent Entry
Guest arrives at venue
↓
Scans QR code at gate
↓
Completes verification on phone
↓
Gate system receives webhook
↓
Access grantedDevice Management
Activating/Deactivating
Devices can be temporarily disabled without deleting:
- Go to Devices
- Find the device
- 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_visitedvalidation.biometry_startedvalidation.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
- Use meaningful names: "Main Entrance Gate" not "Device 1"
- Set an appropriate Session Expiration: Longer for complex flows, shorter for quick checks
- Monitor webhook delivery: Ensure your endpoint is reliable, treat every webhook as possibly the only delivery, and reconcile by polling
- Implement fallbacks: Handle cases when Bouncer is unreachable, and handle a
429with backoff - 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
Create a device session link
Returns a short-lived link and its QR code for one of your devices (a kiosk, vending machine or point of sale). Scanning it starts a new session with the device's settings. Send Accept: application/json, and Content-Type: application/json when you send a body.
Webhooks
Receive real-time notifications for verification events