Automette docs
Delivery

Webhooks

Automette POSTs a JSON payload to your endpoint when something happens in your team — a render finishes, a render fails, or a form is submitted. Every payload from a registered endpoint is signed so you can verify it came from us.

Manage endpoints under Platform → Webhooks, on a template's page (template-scoped), in a form's builder (form-scoped), or via the webhooks API.

Events

EventFires when
render.completedA single render finished successfully (API, on-demand, automation, form). Bulk/CSV rows do not fire this — use job.completed.
render.failedA single render failed or errored.
job.completedA bulk/CSV job finished — one event per job with row counts, instead of a delivery per row.
form.submittedA submission landed on a hosted form.

Delivery tiers

An endpoint's scope decides which events reach it. The tiers are additive — a single render can notify more than one endpoint:

  1. Team-wide — an unscoped endpoint receives every matching event for your team.
  2. Object-scoped — a template-scoped endpoint receives render events for that template only; a form-scoped endpoint receives that form's submissions only. A scoped endpoint never receives another object's events.
  3. Per-request — pass webhook_url on POST /api/v1/renders for a one-off callback tied to that single render. See per-request callbacks below.

Payload envelope

Registered endpoints receive this envelope. event_id is stable across retries — use it to deduplicate.

POST /your-endpoint HTTP/1.1
Content-Type: application/json
webhook-id: evt_9f2a...
webhook-timestamp: 1714000000
webhook-signature: v1,K5o0N...

{
  "event_id": "evt_9f2a...",
  "event": "render.completed",
  "render_id": "cm4rnd...",
  "job_id": "cm4job...",
  "self": "https://automette.com/api/v1/renders/cm4rnd...",
  "status": "completed",
  "url": "https://cdn.automette.com/.../invoice.pdf",
  "format": "pdf",
  "visibility": "public",
  "source": "api",
  "template_id": "cm4tpl...",
  "template_name": "Invoice",
  "data": { "name": "Jane Cooper", "amount": "42" },
  "created_at": "2026-04-22T12:00:00.000Z"
}

render.failed has the same shape with status: "failed" and an error string instead of url/format.

job.completed

A bulk/CSV job emits one summary event when it finishes — not a delivery per row. A partial run reports status: "completed" with a non-zero failed count.

{
  "event_id": "evt_...",
  "event": "job.completed",
  "job_id": "cm4job...",
  "template_id": "cm4tpl...",
  "template_name": "Invoice",
  "source": "csv",
  "status": "completed",
  "total": 5000,
  "completed": 4998,
  "failed": 2,
  "created_at": "2026-04-22T12:00:00.000Z",
  "completed_at": "2026-04-22T12:03:20.000Z"
}

To act on each document in a bulk run individually, open the job in the dashboard or list the template's renders via GET /api/v1/renders?template_id=… — bulk runs deliberately don't send a webhook per row.

form.submitted

{
  "event_id": "evt_form_cm4sub...",
  "event": "form.submitted",
  "form_id": "cm4frm...",
  "submission_id": "cm4sub...",
  "answers": { "email": "jane@example.com", "message": "Hi!" },
  "submitted_at": "2026-04-22T12:00:00.000Z"
}

If the form also generates a document, subscribe to render.completed / render.failed for each file's outcome.

Verifying signatures

Signing follows the Standard Webhooks spec. Compute an HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{raw-body} using your endpoint's secret, base64-encode it, and compare with the webhook-signature header (format v1,<base64>). The secret is shown once, on creation.

import { createHmac, timingSafeEqual } from "crypto"

function verify(req, secret) {
  const id = req.headers["webhook-id"]
  const ts = req.headers["webhook-timestamp"]
  const sig = req.headers["webhook-signature"] // "v1,<base64>"
  const key = Buffer.from(secret.replace(/^wh_/, ""), "base64url")
  const expected = createHmac("sha256", key)
    .update(`${id}.${ts}.${req.rawBody}`)
    .digest("base64")
  const got = sig.split(",")[1]
  return timingSafeEqual(Buffer.from(got), Buffer.from(expected))
}

Reject anything that doesn't verify, and reject stale timestamps (e.g. older than 5 minutes) to prevent replay.

Retries and auto-disabling

A non-2xx response or a timeout (10s) is a failure. Automette retries with exponential backoff — up to 5 attempts per event. After 10 consecutive failures the endpoint is auto-disabled; re-enable it (which resets the counter) once your endpoint is healthy. Deliveries are logged and viewable under Platform → Webhooks.

Per-request callbacks

Passing webhook_url on a render request delivers that render's result to the given URL once. It rides the same delivery pipeline (retries, delivery log) but is unsigned — it carries no webhook-signature header, because an ad-hoc URL has no registered secret. Use a registered endpoint when you need signature verification.

Last updated on

On this page