Server-Side Integration
Integrate biometric age verification into your server-side application using direct API calls.
Prerequisites
- Secret key (
sk_...) and publishable key (pk_...) from your Dashboard - A server capable of making HTTPS requests (Node.js, PHP, Python, etc.)
For API key authentication details and request/response schemas, see the Sessions API Reference.
Quick Start
Server-side integration is straightforward:
- Create a session — Call the API with your secret key
- Redirect users — Send them to the
verifyUrlreturned in the response - Validate results — When users return, call the validate endpoint with the
sessionId
No JavaScript SDK required, no state parameters to sign.
Choose Your Pattern
- JSON pattern (SPA/native):
GET/POST /api/verify-agereturns{ verifyUrl, sessionId }; the client navigates toverifyUrl; later call/api/verify-completeto validate. - Redirect pattern (server‑rendered):
POST /verify-age→res.redirect(verifyUrl);/verifiedroute validatessessionIdand redirects to success/failure pages. - Tip: When testing directly in a browser, use the redirect pattern so you can click a button and follow the flow.
See Redirect Behavior for the parameters appended to your returnUrl.
Environment Setup
Store your secret key in an environment variable and load it at startup:
# .env
SAFEPASSAGE_SECRET_KEY=sk_...
Your secret key is used for authentication. The merchantId field is optional; if you include it, it must match the authenticated key.
// server.js (at top)
require('dotenv').config(); // or: import 'dotenv/config'
Server-Side Flow
1. Create Session
app.get('/api/verify-age', async (req, res) => {
const secretKey = process.env.SAFEPASSAGE_SECRET_KEY;
const response = await fetch('https://api.safepassageapp.com/api/v1/sessions/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${secretKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
returnUrl: 'https://yoursite.com/verified',
externalUserId: req.query.user
})
});
if (!response.ok) {
return res.status(response.status).json({ error: 'Session creation failed' });
}
const session = await response.json();
res.json({ verifyUrl: session.verifyUrl, sessionId: session.sessionId });
});
To redirect immediately instead of returning JSON, use res.redirect(session.verifyUrl).
If you want to open verification in a new tab or popup window from the client:
// Full browser tab
window.open(session.verifyUrl, '_blank');
// Fixed-size popup window
window.open(session.verifyUrl, 'verification', 'width=600,height=700');
When cancelUrl is provided during session creation, closing the verification window in a new-tab flow will attempt to redirect the opener to cancelUrl with status=cancelled and sessionId. You can also listen for the safepassage:verification:complete PostMessage to detect status=cancelled explicitly.
2. Validate Results
When users return to your returnUrl, validate the sessionId server-side:
app.get('/verified', async (req, res) => {
const { sessionId } = req.query;
const response = await fetch('https://api.safepassageapp.com/api/v1/sessions/validate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SAFEPASSAGE_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ sessionId })
});
const result = await response.json();
if (result.accessGranted) {
// Grant access
}
});
The validation response includes externalUserId when provided at session creation, so you can map results back to your user record.
Complete Working Examples
Need a full working sample? Contact support@safepassageapp.com and we can share ready-to-run examples for your stack.
Webhooks
Configure webhooks in your dashboard to get real-time notifications:
app.post('/webhooks/safepassage', async (req, res) => {
const { event, data } = req.body;
if (event === 'verification.completed' && data.externalUserId) {
// Update your user record
await updateUser(data.externalUserId, {
ageVerified: data.verified,
verifiedAt: new Date()
});
}
res.json({ received: true });
});
See Webhooks documentation for details.
Skip Parameters
Streamline the flow for embedded experiences:
| Parameter | Effect |
|---|---|
skip_intro=true | Skip intro screen |
auto_return=true | Skip success screen, redirect immediately |
const url = new URL(session.verifyUrl);
url.searchParams.set('skip_intro', 'true');
url.searchParams.set('auto_return', 'true');
res.redirect(url.toString());
Troubleshooting
| Issue | Solution |
|---|---|
merchantId must match the authenticated API key | Ensure merchantId matches the Authorization header key, or omit merchantId entirely |
| Verification page not loading | Use verifyUrl exactly as returned — don't modify or reconstruct it |
| Validation returns 401 | Use secret key (sk_...) for validation |
| Session expired | Sessions expire after 10 minutes |
| Session already used | Sessions can only be validated once |
| Domain validation error | Add your domain to Dashboard → Settings |
For proxy/iframe embedding, pass the full verifyUrl unchanged.
Support: support@safepassageapp.com