VocaLoop

Webhooks

A webhook pushes events to your system the moment they happen — the push counterpart to polling request status. There are two levels:

  • Project webhook — configured on a project's Settings page; captures events from that project only (submissions, requests, templates, API keys, voice-vendor setup…).

  • Account webhook — configured on Settings → Webhooks; can subscribe to every event in the system, across all projects plus account-level actions (sign-ins, channel integrations, account templates…).

Both show the full list of subscribable events, grouped by category, with checkboxes — tick a whole category or individual events, then save.

Setup

  1. Project webhook: open the project → Settings → Webhook section. Account webhook: Settings → Webhooks.

  2. Enter an http(s) URL, tick the events to capture, and save.

  3. Click Send sample — VocaLoop POSTs a sample payload to the URL and shows you the HTTP status code your endpoint returned.

To try it without writing an endpoint, point the URL at the built-in echo helper http://localhost:3000/api/v1/webhook-echo (dev only — it logs the payload to the backend console and answers {"received": true}), or use a service like webhook.site.

Submission payload

When a project webhook subscribes to Submission received (the default), every submission on the project — whether it came from a sent fill link, the project's direct link, a direct API submission, or a builder preview test — triggers one POST with the full submission:

{
  "event": "project.submission",
  "project_id": "1f00…",
  "project_name": "Job Application",
  "workspace_id": "1f00…",
  "workspace_name": "Job Application",
  "request_id": "9be4…",
  "status": "submitted",
  "channel": "email",
  "recipient": "jane@example.com",
  "data": { "full_name": "Jane Doe", "position": "Engineer" },
  "analytics": { "ip": "203.0.113.7", "timezone": "Asia/Kolkata", "…": "…" },
  "submitted_at": "2026-07-16T09:14:31Z"
}
  • request_id is the same id the send call returned — use it to correlate.

  • channel is where the recipient actually submitted from (email, whatsapp, text, or preview for builder tests); recipient is null for direct-link submissions.

  • data keys are the project's field names.

Event payload

Every other subscribed event (and submissions on the account webhook) is delivered as a compact activity event — the same entries you see on the Usage → Logs page:

{
  "event": "request.sent",
  "actor": "user",
  "detail": "Request sent to jane@example.com",
  "project_id": "1f00…",
  "project_name": "Job Application",
  "context": { "submission_id": "9be4…" },
  "occurred_at": "2026-07-16T09:14:31+00:00"
}
  • event is the dotted action name shown next to each checkbox (project.created, api_key.deleted, account.signed_in, …).

  • actor is who did it: user, api (an API key), or recipient.

  • project_id / project_name are null for account-level events.

  • context carries event-specific extras (ids, channel names, …).

Delivery semantics

  • Best-effort, one attempt — there are no retries. A failure (non-2xx, timeout, unreachable host) is logged on the backend and never blocks or fails the action itself.

  • 10-second timeout — respond quickly; accept the payload and process it asynchronously if your handling is slow.

  • Events are delivered only after the action has actually been committed, so you never hear about something that was rolled back.

  • Because delivery can be missed, treat webhooks as a fast path and status polling as the source of truth for anything critical.

Securing your endpoint

Payloads aren't signed, so:

  • Use an unguessable URL path (it acts as a shared secret) and always HTTPS in production.

  • workspace_id / workspace_name are legacy duplicates of project_id / project_name, kept for receivers built before the projects rename.

  • Validate that project_id matches a project you expect, and treat data as untrusted user input.

  • If you need certainty, confirm via GET /api/v1/external/requests?ids= — the API-key-authenticated read can't be spoofed.