Webhooks

Push events like record.created to your own endpoints as they happen, with signed deliveries and automatic retries.

Updated 2026-09-06

A webhook subscription tells the platform to POST a JSON payload to your endpoint whenever a chosen event happens. It is the push counterpart to polling the API: instead of asking "anything new?", your systems hear about a submission within about a minute of it landing.

Events you can subscribe to

GroupEvents
Recordsrecord.created, record.updated, record.deleted
Formsform.published, form.deleted, form.assigned, form.unassigned
Organizationsorganization.created, organization.updated, organization.deactivated
Usersuser.invited, user.removed, user.roles_updated

A subscription picks any combination of events. Record and form events can also be filtered to specific forms, so an integration that only cares about one inspection form is not flooded by every other form's traffic.

user.invited fires when the invitation is sent, not when it is accepted. The payload always carries the invited email, the granted roles, and who sent the invite. For a person who does not have an account yet, user_id is null and pending is true; when an existing user is added to an organization, user_id is set and pending is false.

What a delivery looks like

Every delivery is an HTTPS POST with a consistent envelope: an event id, the event name, a timestamp, the organization id, and a data object holding the event-specific payload. For record.created, data carries the record's field values, submitter, submission time, device info, and location — the same metadata described in records.

For form.published, data includes the new version number plus a machine-readable summary of the change: which fields were added, removed, and changed, and a classification — identical, compatible, or breaking — so an integration can react to a shape change without diffing schemas itself. The summary is null for a form's first version. What the classification means is covered in form versions.

Deliveries are signed. Each request carries an HMAC-SHA256 signature header computed from the subscription's secret and the request body, plus a timestamp header, so your endpoint can verify the sender and reject replays. The raw secret never travels on the wire.

Your endpoint should respond with a 2xx status within 30 seconds. Heavy processing belongs in your own queue — acknowledge first, work after.

Reshaping an event before delivery

A subscription can run a preprocessing script that rewrites each event before it is sent. Use it to change the payload shape, drop fields the receiver does not need, or add a computed value. The script runs in a sandbox with no network and no filesystem, and it is capped at one second of processing and 10 MB of memory, so it can only reshape the data in front of it. When preprocessing is off, the event is delivered as-is.

You do not have to write the script by hand. The Assist button opens a chat assistant: describe the change you want, and it proposes a script and runs it against a real sample of every event you subscribed to, showing you the result for each one before you apply it. Applying the proposal turns preprocessing on with that script. The assistant needs at least one event selected first, and record events need a form chosen, so it can build the sample from real fields. The full assistant guide is in builder guides.

Retries

A failed delivery retries automatically with a growing delay: roughly 2 minutes after the first failure, then 4, capped at 1 hour. By default a delivery is attempted 3 times (2 automatic retries) before it is marked permanently failed. Every failed response retries the same way — there is no special handling by status code yet, so an endpoint that rejects a payload outright will still see it again until the attempts run out.

flowchart TD E["Something happens<br/>(record submitted)"] --> Q["Event queued"] Q --> T1["Delivery attempt"] T1 -->|2xx response| OK["Delivered"] T1 -->|failure| W1["Wait ~2 minutes"] W1 --> T2["Attempt 2"] T2 -->|failure| W2["Wait ~4 minutes"] W2 --> T3["Attempt 3"] T3 -->|failure| F["Marked failed<br/>(kept in history)"] T2 -->|2xx| OK T3 -->|2xx| OK

The dashboard shows each subscription's delivery history and counts — total, successful, and failed — so a misbehaving endpoint is visible at a glance. You can also send a test event from the dashboard to confirm wiring before real traffic flows.

Delivery history is kept for 90 days. A daily job removes each delivery — and the payload it carried — 90 days after it succeeded or failed for the last time. Deliveries still waiting or still retrying are left alone. If you need a longer record of what was sent, store it on your own side when it arrives.

The API exception

Mutations made through the API do not fire webhooks. If your integration creates a record by calling the API, your record.created subscription stays silent for that record — you already have the response body, and skipping the event prevents feedback loops. Writes made through the MCP server by an AI tool are treated the same way, for the same reason. Dashboard and mobile activity fires normally. The full reasoning is in webhooks and the API.

This is the one thing to remember when testing: a subscription you have just created will not deliver anything if you then create a record through the API or an AI tool. Use Send test event, or submit from the mobile app or the dashboard.

Beyond plain URLs

A subscription can also target a report template — turning any event into a finished PDF or HTML document — or a connector destination that writes matching events into your own SQL database (Postgres, MySQL, or SQL Server) or Salesforce.

A report decides for itself where its finished document goes: a report can have several destinations, and each is delivered on its own. Two of those destination types send events of their own, in the same envelope as everything on this page and signed the same way, so the same verifying code works for all of them:

EventSent byWhat it carries
document.rendereda report's webhook destinationa short-lived link to fetch the file
report.delivery.succeededa report's Box or Google Drive destination, when a callback URL is setthe file id, folder path and link in a provider block keyed on provider.type (box or google_drive), plus the data the document was merged from

Neither is something you subscribe to: each goes to the URL configured on the destination. Reports covers both payloads.