Auditmark

Webhooks

Receive Auditmark events at your own HTTPS endpoint, verified with a per-endpoint signing secret.

Webhooks deliver Auditmark events to an HTTPS endpoint you host, so an external system gets notified in real time when an inspection changes. You add and manage endpoints in the admin app at app.auditmark.io under Settings, on the Webhooks tab. Webhook management is admin-only and is not part of the /v1 API key surface.

Managing endpoints

The Webhooks tab lists every endpoint with its URL, the events it subscribes to, a status dot, and when it was added. Use Add endpoint to create one. Each endpoint row has a Send test menu (Ping, or a sample of any subscribed event) and a menu with View deliveries, Edit endpoint, Regenerate secret, and Delete. View deliveries and Edit endpoint open the same endpoint view, on its Recent deliveries tab and its Settings tab respectively.

When you add or edit an endpoint you set:

FieldWhat it does
Endpoint URLThe public https:// URL Auditmark posts to.
DescriptionAn optional label, for example the name of the receiving system.
EventsThe event types this endpoint receives. Use Select all or Clear all to toggle every subscribable event at once.
ActiveWhether Auditmark delivers events to this endpoint. Turn it off to pause delivery without deleting the endpoint.

An endpoint must subscribe to at least one event before you can save it.

Endpoint requirements

Your endpoint must use HTTPS. Auditmark rejects any URL that resolves to a private, loopback, link-local, reserved, multicast, or unspecified address, and it re-checks the destination IP on each delivery to guard against SSRF and DNS rebinding. If a URL fails these checks, the form shows an error and the endpoint is not saved.

Event types

You can subscribe an endpoint to any of these events:

EventWhen it fires
inspection.startedAn inspection moved to in progress.
inspection.completedAn inspection was completed.
inspection.pending_approvalAn inspection is awaiting approval.
inspection.pending_sign_offAn inspection is awaiting sign-off.
inspection.archivedAn inspection was finalized (archived).
inspection.correction_openedA finalized inspection was reopened for correction.
inspection.correctedA correction on a finalized inspection was completed. The status field is the inspection's status before the correction (completed or archived).
directory.user.left_sourceAn external-directory user left the source and is flagged for admin review.
report.readyA report finished generating and is ready to fetch.
inspection.triggeredA follow-up inspection was spawned by an answer-triggered rule.
corrective_action.createdA corrective action was created. The two assignee fields are null when the action is unassigned. The label field is null when the finding has no label.
corrective_action.resolvedA corrective action was submitted as resolved by its assignee. The label field is null when the finding has no label.
corrective_action.reopenedA corrective action was reopened for further work. The label field is null when the finding has no label.
corrective_action.closedA corrective action was closed (verified or auto-closed). The label field is null when the finding has no label.
corrective_action.updatedA corrective action's priority, due date, assignee or resolution notes changed. The changed field names are listed in the changed array; due_date, assignee_user_id and assignee_kind carry their new values. The label field is null when the finding has no label.

Payload by event

Each example below is the full delivery body. data is the event's payload; a value shown as "uuid" is an identifier and "string" is free text. A sample sent from the admin app has the same shape with random identifiers and test set to true.

inspection.started

An inspection moved to in progress.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.started",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "resource": "inspection",
    "status": "in_progress"
  }
}

inspection.completed

An inspection was completed.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.completed",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "resource": "inspection",
    "status": "completed"
  }
}

inspection.pending_approval

An inspection is awaiting approval.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.pending_approval",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "resource": "inspection",
    "status": "pending_approval"
  }
}

inspection.pending_sign_off

An inspection is awaiting sign-off.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.pending_sign_off",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "resource": "inspection",
    "status": "pending_sign_off"
  }
}

inspection.archived

An inspection was finalized (archived).

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.archived",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "resource": "inspection",
    "status": "archived"
  }
}

inspection.correction_opened

A finalized inspection was reopened for correction.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.correction_opened",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "resource": "inspection",
    "status": "in_progress",
    "correction_id": "uuid"
  }
}

inspection.corrected

A correction on a finalized inspection was completed. The status field is the inspection's status before the correction (completed or archived).

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.corrected",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "resource": "inspection",
    "status": "completed",
    "correction_id": "uuid"
  }
}

directory.user.left_source

An external-directory user left the source and is flagged for admin review.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "directory.user.left_source",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "candidate_id": "uuid",
    "resource": "directory_user",
    "email": "user@example.com",
    "source_status": "left_source"
  }
}

report.ready

A report finished generating and is ready to fetch.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "report.ready",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "report_id": "uuid",
    "inspection_id": "uuid",
    "resource": "report",
    "status": "ready"
  }
}

inspection.triggered

A follow-up inspection was spawned by an answer-triggered rule.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "inspection.triggered",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "id": "uuid",
    "parent_inspection_id": "uuid",
    "resource": "inspection",
    "status": "scheduled"
  }
}

corrective_action.created

A corrective action was created. The two assignee fields are null when the action is unassigned. The label field is null when the finding has no label.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "corrective_action.created",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "corrective_action_id": "uuid",
    "finding_id": "uuid",
    "inspection_id": "uuid",
    "resource": "corrective_action",
    "priority": "high",
    "status": "open",
    "assignee_user_id": "uuid",
    "assignee_kind": "member",
    "label": {
      "id": "uuid",
      "name": "string"
    }
  }
}

corrective_action.resolved

A corrective action was submitted as resolved by its assignee. The label field is null when the finding has no label.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "corrective_action.resolved",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "corrective_action_id": "uuid",
    "finding_id": "uuid",
    "inspection_id": "uuid",
    "resource": "corrective_action",
    "status": "resolved",
    "priority": "high",
    "label": {
      "id": "uuid",
      "name": "string"
    }
  }
}

corrective_action.reopened

A corrective action was reopened for further work. The label field is null when the finding has no label.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "corrective_action.reopened",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "corrective_action_id": "uuid",
    "finding_id": "uuid",
    "inspection_id": "uuid",
    "resource": "corrective_action",
    "status": "in_progress",
    "priority": "high",
    "label": {
      "id": "uuid",
      "name": "string"
    }
  }
}

corrective_action.closed

A corrective action was closed (verified or auto-closed). The label field is null when the finding has no label.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "corrective_action.closed",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "corrective_action_id": "uuid",
    "finding_id": "uuid",
    "inspection_id": "uuid",
    "resource": "corrective_action",
    "status": "closed",
    "priority": "high",
    "label": {
      "id": "uuid",
      "name": "string"
    }
  }
}

corrective_action.updated

A corrective action's priority, due date, assignee or resolution notes changed. The changed field names are listed in the changed array; due_date, assignee_user_id and assignee_kind carry their new values. The label field is null when the finding has no label.

{
  "event_id": "3f0c9e4a-6d2b-4c1e-9a7f-1b2c3d4e5f60",
  "event_type": "corrective_action.updated",
  "created_at": "2026-09-03T12:00:00+00:00",
  "organization_id": "8a1d2e3f-4b5c-4d6e-8f70-91a2b3c4d5e6",
  "test": false,
  "data": {
    "corrective_action_id": "uuid",
    "finding_id": "uuid",
    "inspection_id": "uuid",
    "resource": "corrective_action",
    "status": "open",
    "priority": "high",
    "changed": [
      "due_date",
      "assignee_user_id"
    ],
    "due_date": "2027-01-15",
    "assignee_user_id": "uuid",
    "assignee_kind": "member",
    "label": {
      "id": "uuid",
      "name": "string"
    }
  }
}

A ping event is sent when you choose Ping from Send test on an endpoint. It is not a subscribable type and its data is {"resource": "ping"}, so you can use it to confirm your receiver is reachable and your signature check works.

Sending a sample

Open Send test on an endpoint row to choose Ping or a sample of any event the endpoint subscribes to. A sample is a real delivery: it is signed, retried, and logged like any other, and its data has the shape documented above with random identifiers that resolve to nothing. When you edit an endpoint, each checked event also has a Send sample button and a Payload disclosure that shows the example, so you can wire a receiver before the first real event arrives.

Samples and pings carry "test": true in the body and an X-Auditmark-Test: true header. Real events carry "test": false and no such header, so a receiver can route samples with one check.

Delivery format

Each delivery is a POST request with a JSON body. The body is an envelope:

{
  "event_id": "…",
  "event_type": "inspection.archived",
  "created_at": "2026-06-23T12:00:00+00:00",
  "organization_id": "…",
  "test": false,
  "data": { }
}

The data field holds the event-specific payload; see Payload by event for every shape. test is true only on a ping or a sample sent from the admin app.

Auditmark sends these headers:

HeaderValue
Content-Typeapplication/json
X-Auditmark-EventThe event type, for example inspection.archived.
X-Auditmark-DeliveryA unique delivery ID you can use for idempotency.
X-Auditmark-Testtrue, sent only on a ping or a sample. Absent on real events.
X-Auditmark-Signaturet=<timestamp>,v1=<hex>, an HMAC-SHA256 over <timestamp>.<body>.
X-Auditmark-Signature-256sha256=<hex>, an HMAC-SHA256 over the raw body.

The X-Auditmark-Signature header includes the request timestamp in the signed content, so you can detect and reject replays by checking that the timestamp is recent.

Verifying a delivery

Each endpoint has its own signing secret. It is shown once when you create the endpoint, and you can regenerate it at any time from the endpoint menu. The secret starts with whsec_. Store it somewhere safe, because it is not shown again. Recompute the HMAC and compare it to the header before you process the payload.

import hmac
import hashlib

def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

# Usage
is_valid = verify_signature(
    secret=YOUR_ENDPOINT_SECRET,
    raw_body=request.body,          # raw bytes, before any JSON parsing
    signature_header=request.headers["X-Auditmark-Signature-256"],
)

X-Auditmark-Signature-256 follows the same convention as GitHub's X-Hub-Signature-256, so libraries and receivers that support that header (for example Make, Zapier, and n8n) work here too.

Regenerating the secret takes effect immediately: the old secret stops working, so update any system that verifies signatures before you rely on the new one.

Retries and failure handling

Auditmark retries a delivery that does not receive a 2xx response. The retry schedule uses exponential backoff starting at 30 seconds, capped at 3600 seconds (one hour), for up to 12 attempts before the delivery is marked failed.

After 20 consecutive failures on an endpoint, Auditmark auto-disables it and records the reason on the endpoint. Re-enabling the endpoint (set Active back on) clears the failure count and resumes delivery.

Auditmark logs every delivery attempt. Open an endpoint and switch to its Recent deliveries tab to see them with their status (Pending, Delivered, or Failed), HTTP code, attempt count, latency, and time. A ping or a sample is marked Test in the log. A failed attempt also shows the error your endpoint returned. Use Replay on a row to requeue that delivery.

Responding to deliveries

Return a 2xx status code as quickly as possible. If your processing takes time, accept the delivery immediately and handle it asynchronously. Use X-Auditmark-Delivery to deduplicate replays on your side.

See also

On this page