Node.js Examples

Node.js examples for Loom API v1 using fetch.

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
}

More