Node.js Examples
Use the Loom API v1 with Node.js via fetch (Node 18+) or node-fetch. No SDK required. 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
async function startVerification(metadata = {}) {
const res = await fetch(`${API_BASE}/verify/start`, {
method: 'POST',
headers: apiHeaders(),
body: JSON.stringify(metadata),
})
if (!res.ok) {
const err = await res.json().catch(() => ({}))
throw new Error(err.error?.message || res.statusText)
}
return res.json()
}
// Usage
const { verificationId, sessionUrl, redirectUrl } = await startVerification({
user_id: 'user_123',
})
console.log('Send user to:', sessionUrl || redirectUrl)
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()
}
// Usage
const status = await getVerificationStatus('verif_abc123')
if (status.status === 'completed' && status.result?.verified) {
console.log('Verified age:', status.result.verifiedAge)
}
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.error?.message || 'Invalid token')
}
return res.json()
}
// Usage (e.g. in callback after user completes session)
const result = await validateToken(tokenFromCallback)
if (result.valid) {
console.log('Verified age:', result.verifiedAge)
}
Express: redirect to verification
const express = require('express')
const app = express()
app.get('/verify', async (req, res) => {
try {
const { verificationId, sessionUrl, redirectUrl } = await startVerification({
user_id: req.user?.id,
})
const url = sessionUrl || redirectUrl
res.redirect(url)
} catch (err) {
res.status(500).json({ error: err.message })
}
})
// Callback after user completes verification (token in query or body)
app.get('/verify/callback', async (req, res) => {
const token = req.query.token
if (!token) return res.status(400).send('Missing token')
try {
const result = await validateToken(token)
if (result.valid) {
// Update user, set cookie, etc.
res.redirect('/dashboard')
} else {
res.redirect('/verify?error=invalid')
}
} catch (err) {
res.redirect('/verify?error=validation_failed')
}
})
Webhook handler (signature verification)
const crypto = require('crypto')
function verifyWebhookSignature(payload, signature, secret) {
if (!signature) return false
const [tPart, v1Part] = signature.split(',')
const timestamp = tPart?.split('=')[1]
const expectedSig = v1Part?.split('=')[1]
if (!timestamp || !expectedSig) return false
const signedPayload = `${timestamp}.${typeof payload === 'string' ? payload : JSON.stringify(payload)}`
const calculated = crypto.createHmac('sha256', secret).update(signedPayload, 'utf8').digest('hex')
return crypto.timingSafeEqual(Buffer.from(calculated, 'hex'), Buffer.from(expectedSig, 'hex'))
}
app.post('/webhooks/loom', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-loom-signature']
const secret = process.env.LOOM_WEBHOOK_SECRET
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Unauthorized')
}
const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
const { event, verificationId, verification } = body
if (event === 'verification.completed') {
// Update user by verificationId, use verification.verifiedAge etc.
} else if (event === 'verification.failed') {
// Handle failure
}
res.status(200).send('OK')
})
Error handling
async function startVerificationWithRetry(metadata, maxRetries = 3) {
let lastErr
for (let i = 0; i < maxRetries; i++) {
try {
return await startVerification(metadata)
} catch (err) {
lastErr = err
if (err.message?.includes('RATE_LIMIT') || err.message?.includes('429')) {
await new Promise((r) => setTimeout(r, 5000))
continue
}
throw err
}
}
throw lastErr
}