Node.js Examples
Official SDK available. If you'd rather not write the HTTP layer yourself, install
loomapi-js— it covers retries, typed errors, and webhook verification out of the box. See SDKs.
The examples below show the raw fetch API (Node 18+). Auth: x-tenant-api-key header. Base URL: https://api.loomapi.com.
Configuration
const API_BASE = 'https://api.loomapi.com'
const API_KEY = process.env.LOOM_TENANT_API_KEY
function apiHeaders() {
return {
'x-tenant-api-key': API_KEY,
'Content-Type': 'application/json',
}
}
Start a verification
The request body is optional. Only userAgent and ip are accepted (unknown fields are rejected).
async function startVerification(opts = {}) {
const res = await fetch(`${API_BASE}/verify/start`, {
method: 'POST',
headers: apiHeaders(),
body: JSON.stringify(opts), // e.g. { userAgent: '...', ip: '...' }
})
if (!res.ok) {
const err = await res.json().catch(() => ({}))
throw new Error(err.message || res.statusText)
}
return res.json()
// Returns: { verificationId, status: 'started', provider: 'idenfy', sessionUrl, config }
}
// Usage
const { verificationId, sessionUrl } = await startVerification()
console.log('Send user to:', sessionUrl)
Get verification status
async function getVerificationStatus(verificationId) {
const res = await fetch(
`${API_BASE}/verify/status?verificationId=${encodeURIComponent(verificationId)}`,
{ headers: apiHeaders() }
)
if (!res.ok) throw new Error(await res.text())
return res.json()
// Returns: { verificationId, status, confidence, createdAt, completedAt }
}
// Usage
const status = await getVerificationStatus('cjld2cjxh0000qzrmn831i7rn')
// status.status is one of: pending | started | submitted | approved | denied | resubmission | rejected
if (status.status === 'approved') {
console.log('User verified with confidence:', status.confidence)
}
Validate a token
async function validateToken(token) {
const res = await fetch(`${API_BASE}/tokens/validate`, {
method: 'POST',
headers: apiHeaders(),
body: JSON.stringify({ token }),
})
if (!res.ok) {
const err = await res.json().catch(() => ({}))
throw new Error(err.message || 'Validation failed')
}
return res.json()
// Returns: { valid: boolean, over18: boolean, reason: string }
}
// Usage
const result = await validateToken(jwtFromSession)
if (result.valid && result.over18) {
// Grant access
} else {
console.log('Rejected:', result.reason)
// reason: TOKEN_EXPIRED | TOKEN_REVOKED | TOKEN_NOT_FOUND | UNDERAGE_OR_UNKNOWN
}
Express: redirect to verification
const express = require('express')
const app = express()
app.get('/verify', async (req, res) => {
try {
const { verificationId, sessionUrl } = await startVerification({
userAgent: req.headers['user-agent'],
ip: req.ip,
})
// Store verificationId in session if needed
res.redirect(sessionUrl)
} catch (err) {
res.status(500).json({ error: err.message })
}
})
Webhook handler (signature verification)
LoomAPI sends X-Webhook-Signature (raw HMAC-SHA256 hex) and X-Webhook-Event. Always verify on the raw request body before parsing JSON.
const crypto = require('crypto')
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex')
if (signature.length !== expected.length) return false
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
)
}
app.post('/webhooks/loom', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'] // NOT x-loom-signature
const secret = process.env.LOOM_WEBHOOK_SECRET
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Unauthorized')
}
const payload = JSON.parse(req.body)
// payload: { event, tenantId, data: { verificationId, status, confidence, provider }, timestamp }
if (payload.event === 'verification.completed') {
const { verificationId, status } = payload.data
if (status === 'approved') {
// Grant access — user is verified
} else {
// status is denied, resubmission, rejected, etc.
}
}
res.status(200).send('OK')
})
Error handling
async function startVerificationWithRetry(opts = {}, maxRetries = 3) {
let lastErr
for (let i = 0; i < maxRetries; i++) {
try {
return await startVerification(opts)
} catch (err) {
lastErr = err
if (err.message?.includes('429')) {
await new Promise((r) => setTimeout(r, 5000))
continue
}
throw err
}
}
throw lastErr
}