Skip to content
NEWSADA Title II web deadlines: April 24, 2026 (50k+ pop) · April 26, 2027 (under 50k) — Is your site compliant?ADA Title II: April 2026 & 2027 deadlinesLearn more →

Webhooks

Angstroma can send HTTP POST requests to your endpoint when events occur in your account — scan completions, profile updates, plan changes, and more. Webhooks are configured in the portal under Settings → Organization.

Delivery

Angstroma uses an outbox pattern — each event is persisted before delivery is attempted. If your endpoint returns a non-2xx status, delivery is retried with exponential backoff. Failed deliveries can be retried or replayed from the Webhooks page in the portal.

Your endpoint must respond within 10 seconds or the delivery is treated as failed. Return 200 OK as quickly as possible — process the payload asynchronously.

Tip
Use the Send test webhook button in the portal to verify your endpoint before going live. The test event type is webhook.test.

Payload format

All webhook payloads are JSON with a consistent structure:

{
  "event": "scan.completed",
  "timestamp": "2025-04-12T14:23:00Z",
  "tenantSlug": "your-org",
  "data": {
    // event-specific data
  }
}

Event types

EventWhen it fires
webhook.testManual test from the portal
scan.completedA WCAG scan job has finished
scan.failedA WCAG scan job failed
profile.updatedA user accessibility profile was updated
preset.appliedA preset was applied to a user profile
iep.processedAn IEP document has been parsed and mapped
iep.failedIEP processing failed
plan.upgradedTenant plan was upgraded
plan.downgradedTenant plan was downgraded
api_key.revokedAn API key was revoked
member.invitedA new team member was invited
member.removedA team member was removed

Example payloads

scan.completed

{
  "event": "scan.completed",
  "timestamp": "2025-04-12T14:23:00Z",
  "tenantSlug": "your-org",
  "data": {
    "jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "url": "https://example.com",
    "pagesScanned": 12,
    "issuesFound": 47,
    "criticalIssues": 3,
    "wcagLevel": "AA",
    "completedAt": "2025-04-12T14:22:58Z"
  }
}

iep.processed

{
  "event": "iep.processed",
  "timestamp": "2025-04-12T09:00:00Z",
  "tenantSlug": "your-org",
  "data": {
    "documentId": "8a1b2c3d-...",
    "studentId": "stu_456",
    "accommodationsFound": 8,
    "featuresActivated": 5
  }
}

Verifying webhook signatures

Angstroma signs each webhook payload with an HMAC-SHA256 signature using your webhook secret. The signature is sent in the X-Angstroma-Signature header.

Important
Always verify the signature before processing a webhook. This prevents attackers from sending fake events to your endpoint.
webhook-handler.ts
import crypto from 'crypto'

export function verifyWebhook(
  payload: string,         // raw request body (string, not parsed)
  signature: string,       // X-Angstroma-Signature header value
  secret: string           // your webhook secret from portal settings
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  )
}

// In your route handler (Next.js example):
export async function POST(req: Request) {
  const payload = await req.text()
  const signature = req.headers.get('x-angstroma-signature') ?? ''

  if (!verifyWebhook(payload, signature, process.env.ANGSTROMA_WEBHOOK_SECRET!)) {
    return new Response('Unauthorized', { status: 401 })
  }

  const event = JSON.parse(payload)
  // process event...

  return new Response('OK')
}