Webhooks

Webhooks push wipe and certificate events from AVERASE to your systems the moment they happen. Each webhook subscribes one endpoint to one event type; every delivery is signed with the webhook's secret so your receiver can prove it came from AVERASE.

What webhooks push

There are six event types, covering the full life of a session — from the moment a wipe is authorized to the moment its certificate PDF is ready:

Event typeFires when
wipe_session_readyA pre-wipe check-in is allowed and a wipe is about to start
wipe_session_blockedA boot verification or check-in is denied
asset_reportedA machine boots from AVERASE for the first time
hardware_tests_completedAn operator submits hardware test results
wipe_session_completedA wipe result arrives — success or failure
certificate_generatedA certificate of erasure is signed and its PDF uploaded

A webhook subscribes to exactly one event type. To receive several event types, create one webhook per type — they can all point at the same URL, and the X-Erase-Engine-Event header tells your receiver which one fired.


Creating a webhook

Webhooks are managed by account admins under Settings → Webhooks in the web application. The index lists every webhook with its Event type, URL, Status (Active, Disabled, or Auto-disabled — "off after N straight failures"), Last triggered time, and an Edit link, plus a New Webhook button in the header band. As the page itself puts it:

Push wipe and certificate events to your systems the moment they happen. Each webhook subscribes one endpoint to one event type; payloads are signed with the webhook's secret (X-Erase-Engine-Signature).

Form fields

  • Name
    Event type
    Type
    select
    Description

    One of the six event types. One event type per webhook.

  • Name
    URL
    Type
    string
    Description

    Your receiving endpoint. Must be https:// — plain HTTP is rejected. Placeholder: https://example.com/webhooks/erase-engine.

  • Name
    Auth type
    Type
    select
    Description

    None (signature only), Bearer token, or Basic auth. The form explains it as: "How we authenticate to YOUR endpoint (separate from our payload signature)." When Bearer or Basic is selected, deliveries carry an Authorization header in addition to the signature. Credentials are encrypted at rest; leaving a secret field blank when editing keeps the stored value.

  • Name
    Active
    Type
    checkbox
    Description

    Shown when editing. "Inactive webhooks receive nothing; re-enabling redelivers held events."

The signing secret

Every webhook gets an auto-generated signing secret (64 hex characters) the moment it is created — you never choose it. The edit page shows the secret in full, along with the verification recipe:

Verify each delivery by computing HMAC-SHA256 over "{X-Erase-Engine-Timestamp}.{raw body}" with this secret and comparing it to X-Erase-Engine-Signature (format: sha256=hex).

Send test event

The edit page's Send test event button POSTs a synthetic ping to your endpoint synchronously — same three signing headers as a real delivery, so you can exercise your verification code end to end. The test body carries no record fields:

Test ping body

{
  "event_id": "f6a7b8c9-d0e1-4234-9f4a-5b6c7d8e9f0a",
  "event_type": "wipe_session_completed",
  "test": true,
  "created_at": "2026-08-12T17:00:05Z"
}

Test pings are not recorded in the delivery log. The result comes back immediately as a flash: "Test event delivered — your endpoint answered 200." on success, or "Test event failed: <error>" on failure.

Deleting a webhook

Delete asks for confirmation: "Delete this webhook? Undelivered events for it will stop retrying." Deleting a webhook abandons any deliveries still in its retry queue.

Delivery log

Below the webhook list, a delivery log shows recent deliveries: Event (event type plus the event_id in monospace), Endpoint, Status (delivered / failed / pending), and When.


The envelope

Every delivery is a single JSON object: three envelope fields, then the record's fields flat at the top level — there is no nested payload or data wrapper, and no batching (one record per POST).

Envelope shape (truncated)

{
  "event_id": "d4e5f6a7-b8c9-4012-9d2e-3f4a5b6c7d8e",
  "event_type": "wipe_session_completed",
  "created_at": "2026-08-12T17:41:08Z",

  "id": "wr_01J4R2M8K3P7QS5TV9WX6YZABC",
  "type": "wipe_report",
  "updated_at": "2026-08-12T17:41:08Z",
  "revision": 1786815668,
  "serial": "S64FNE2R504071",
  "status_display": "Completed"
}
  • Name
    event_id
    Type
    uuid
    Description

    Unique per delivery. Retries of the same delivery reuse it — dedupe on this field.

  • Name
    event_type
    Type
    string
    Description

    One of the six event types; also sent as the X-Erase-Engine-Event header.

  • Name
    created_at
    Type
    timestamp
    Description

    When the event was created. Record fields are spread after the envelope fields, so if the record carries its own created_at, the record's value wins.

Every record additionally carries id (its stable identifier — asset_…, wr_…, cert_…, or a job UUID), type (the record type), updated_at, and revision (the Unix-seconds form of updated_at) — the keys your upsert logic relies on. See Payloads for the full record contract.


Verifying deliveries

Every delivery carries three signing headers:

HeaderValue
X-Erase-Engine-TimestampUnix seconds at send time
X-Erase-Engine-Signaturesha256= + lowercase hex HMAC-SHA256 over "{timestamp}.{raw body}", keyed with the webhook's signing secret
X-Erase-Engine-EventThe event type, e.g. wipe_session_completed

Content-Type is always application/json, and an Authorization: Bearer … or Basic … header is added when the webhook's auth type calls for it.

The signature covers the exact JSON bytes of the body — retries resend the same frozen bytes, so the signature stays stable across attempts. Verify against the raw request body before parsing it; a re-serialized body will not match. Use a constant-time comparison, and reject timestamps more than 300 seconds from your clock to shut down replay.

Verifying a delivery

const crypto = require('node:crypto')

// rawBody must be the exact request bytes — verify BEFORE JSON.parse.
function verify(req, rawBody, signingSecret) {
  const timestamp = req.headers['x-erase-engine-timestamp']
  const signature = req.headers['x-erase-engine-signature']

  const expected =
    'sha256=' +
    crypto
      .createHmac('sha256', signingSecret)
      .update(`${timestamp}.${rawBody}`)
      .digest('hex')

  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300
  const valid =
    signature &&
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))

  return fresh && valid
}

A delivery counts as successful when your endpoint answers with any 2xx status within 10 seconds (connection open 5 s, read/write 10 s). Anything else — a non-2xx status, a timeout, a connection failure — counts as a failed attempt.


Delivery and retries

Deliveries are durable. A ledger row is written for every (webhook, event) pair before the first attempt, so a crash mid-send can never lose an event — it can only leave it pending, where reconciliation picks it up.

  • 8 attempts per delivery, spaced with polynomial backoff (plus jitter), spanning roughly 90 minutes end to end.
  • Per-endpoint serialization: each webhook delivers one event at a time, so a slow endpoint queues rather than getting hammered concurrently. Ordering across event types is still not guaranteed — treat every event as an upsert.
  • A successful delivery resets the webhook's consecutive-failure count to zero.
  • A delivery that exhausts all 8 attempts is marked failed and counts one consecutive failure against the webhook.

Auto-disable and recovery

After 5 consecutive exhausted deliveries — five whole deliveries, each having burned all 8 attempts — the webhook is disabled automatically and its status shows Auto-disabled in Settings → Webhooks. The edit page explains:

This webhook was disabled automatically on <date> after <count> consecutive delivery failures. Fix the endpoint, then re-enable it below — held events will be redelivered.

Fix your endpoint, then tick Active and save. Re-enabling resets the failure count and triggers reconciliation, which redelivers the events your endpoint missed.

Reconciliation and backfill

A reconciliation job sweeps up anything the retry pipeline could not finish:

  • Re-queues deliveries marked failed, and any left pending for more than an hour (e.g. after a crash between ledger write and first attempt).
  • Backfills records that never got a delivery row at all — in batches of 500 — for the four record-shaped events: asset_reported, hardware_tests_completed, wipe_session_completed, and certificate_generated (certificates are only backfilled once their pdf_url is set).

Backfilled and re-queued deliveries get new event_ids.


Subscriber best practices

  • Dedupe on event_id. Retries reuse it; reconciliation mints new ones — so the same underlying record can arrive under more than one event_id. Which is why you also:
  • Upsert on id + revision. Apply a record when its revision is greater than or equal to the one you have stored; ignore stale ones.
  • Don't rely on ordering. Deliveries to one endpoint are serialized, but ordering across event types is not guaranteed — certificate_generated can plausibly land before the wipe_session_completed it belongs to.
  • Expect bursts after outages. When your endpoint recovers or a webhook is re-enabled, reconciliation redelivers everything held — potentially hundreds of events in quick succession.
  • Enforce the ±300 s timestamp window and a constant-time signature comparison, as in the snippets above.
  • Respond 2xx fast. You have 10 seconds; queue the payload and process it asynchronously rather than doing work inline.

Testing

The sandbox runs the same event types, envelope, signing headers, and record schemas locally — including a live webhook viewer that shows every delivery, its headers, and whether its signature verifies. It is the fastest way to build a receiver before pointing production at it.

Was this page helpful?