Sprinter Docs

Webhooks

Outbound HTTP webhook endpoints for entity lifecycle events, with HMAC-SHA256 signature verification, Inngest-driven delivery, and automatic failure tracking.

Webhooks

Webhooks deliver HTTP POST notifications to external systems when events occur in the platform. Administrators configure endpoint URLs, select which events to subscribe to, and provide a secret for signature verification. Delivery is handled asynchronously via Inngest with retry on failure.

Overview

The module lives in features/webhooks/ and provides the data layer (types, server actions, signature utilities) and admin UI. Webhook delivery is handled by the features/inngest/functions/webhook-delivery.ts Inngest function, which sends the HTTP request, verifies success, and increments the failure counter on error.

Webhooks are also used as action side effects: field-population lifecycle events (on_populate, on_approve, on_reject) can trigger webhook deliveries when configured for an action.

Key Concepts

WebhookEndpoint -- The database record for a configured endpoint:

  • url -- the destination URL that receives POST requests
  • secret -- shared secret for HMAC-SHA256 signature generation
  • events -- array of event types this endpoint subscribes to
  • enabled -- toggle to pause delivery without deleting the endpoint
  • failure_count -- incremented on delivery failure, visible in the admin UI
  • description -- optional human-readable label
  • last_triggered_at -- timestamp of last successful delivery

WebhookEndpointView -- The WebhookEndpoint type with the secret field omitted. Used in API responses and admin UI to avoid exposing secrets.

Available Events -- The platform defines five webhook event types:

  • entity/created -- a new entity record was created
  • entity/updated -- an entity was modified
  • entity/deleted -- an entity was removed
  • document/uploaded -- a document was uploaded
  • agent/heartbeat -- an agent heartbeat run completed

Signature Verification -- Every webhook payload is signed with HMAC-SHA256 using the endpoint's secret. The signature is included as a header so receivers can verify authenticity.

How It Works

Configuration

Administrators manage webhooks via the Admin panel. The WebhookAdmin component provides:

  1. A list of configured endpoints showing URL, subscribed events, enabled status, and failure count
  2. A dialog for creating new endpoints (URL, secret, description, event selection)
  3. Toggle switches to enable/disable endpoints
  4. Delete buttons for removal

Delivery Pipeline

  1. An event occurs in the platform (entity created, document uploaded, etc.)
  2. The Inngest function webhook-delivery receives the event
  3. It queries webhook_endpoints for enabled endpoints subscribed to this event type
  4. For each matching endpoint:
    • Serialize the event payload as JSON
    • Generate HMAC-SHA256 signature using signPayload(payload, secret)
    • Send HTTP POST to the endpoint URL with the payload and signature header
    • On success: update last_triggered_at
    • On failure: increment failure_count

Signature Verification

The features/webhooks/lib/signature.ts module provides:

  • signPayload(payload, secret) -- generates HMAC-SHA256 hex digest
  • verifySignature(payload, signature, secret) -- constant-time comparison to prevent timing attacks

Receivers should verify the signature header against the payload using their copy of the shared secret.

API Reference

Server Actions

FunctionLocationPurpose
listWebhooks()features/webhooks/server/actions.tsList all endpoints (secrets excluded)
getWebhook(id)SameFetch single endpoint
createWebhook(input)SameCreate endpoint with URL, secret, events
updateWebhook(id, input)SameUpdate endpoint fields
deleteWebhook(id)SameDelete endpoint

Signature Functions

FunctionLocationPurpose
signPayload(payload, secret)features/webhooks/lib/signature.tsGenerate HMAC-SHA256 signature
verifySignature(payload, signature, secret)SameConstant-time signature verification

Types

TypeLocationPurpose
WebhookEndpointfeatures/webhooks/types.tsFull endpoint record
WebhookEndpointViewSameEndpoint without secret
CreateWebhookInputSameCreate input shape
UpdateWebhookInputSamePartial update input

Components

ComponentLocationPurpose
WebhookAdminfeatures/webhooks/components/webhook-admin.tsxFull admin CRUD UI

For Agents

Agents do not directly manage webhooks. Webhook configuration is an admin-only operation. However, agent actions (creating entities, updating records, running heartbeats) generate the events that trigger webhook deliveries.

Agents can also indirectly trigger webhooks through extraction field actions: when a field has an on_populate or on_approve action of type webhook, the webhook delivery pipeline fires after extraction completes.

Inbound Webhooks

The platform also accepts inbound webhooks from external systems and converts them into entity records. This is the push side of external integrations: a third-party service (Acuity Scheduling, Stripe, any SaaS with a webhook feature) posts to a per-tenant endpoint URL; the platform verifies the signature, maps the payload to entity fields, and creates or upserts the entity.

Configuration

Each inbound endpoint is a row in inbound_webhook_endpoints and carries:

  • token — URL-safe random token that uniquely identifies the endpoint (/api/webhooks/inbound/{token})
  • entity_type_slug — which entity type to create/upsert
  • field_mapping — JSON mapping from source payload keys to entity content field names
  • signature_secret — shared secret used to verify the HMAC signature from the sender
  • signature_header — the header name the sender puts the signature in (e.g. x-acuity-signature, stripe-signature)
  • signature_encoding — encoding of the digest in the signature header. hex (default, 64 chars — Stripe, Slack, GitHub, most SaaS) or base64 (44 chars — Acuity Scheduling, Squarespace Scheduling). Mismatch causes verification to short-circuit on length, so set this correctly per provider.

Inbound endpoint rows are created via seed scripts or the Admin > Webhooks UI (inbound tab).

Delivery Route

POST /api/webhooks/inbound/{token}

Accepted content types:

  • application/json — default; body parsed directly as JSON
  • application/x-www-form-urlencoded — form-encoded bodies (Acuity Scheduling, SendGrid, Mailgun, and other legacy SaaS providers); decoded to a flat Record<string, string> before field mapping

Multipart/form-data is intentionally not supported. Route through a JSON-converting intermediary if needed.

HMAC verification runs over the raw wire bytes before content-type decoding, so signature integrity is preserved for all content types.

Acuity Scheduling Example

Acuity sends a skinny webhook on appointment events ({action, id, calendarID, appointmentTypeID}) as application/x-www-form-urlencoded. The DOC'S integration uses this flow:

  1. Acuity posts to /api/webhooks/inbound/{token} with x-acuity-signature header (base64-encoded HMAC-SHA256)
  2. Platform verifies HMAC using the Acuity API key as the shared secret, with signature_encoding: 'base64' set on the endpoint row
  3. field_mapping creates a stub visit with acuity_id and sync_status: 'webhook'
  4. entity_created event fires
  5. Linker action (docs-acuity-link-visit-to-patient) runs: fetches the full appointment from the Acuity REST API via the tenant's agent_connections row, expands the stub, and resolves the visit to a patient record

This skinny-webhook + REST-expansion pattern keeps processInboundWebhook as a lean router and puts the round-trip fetch inside a retryable action with session-event logging.

Design Decisions

  • Raw-bytes HMAC — signature verification runs before content-type parsing so the HMAC covers the unmodified wire representation. This matches what Acuity and most SaaS providers expect.
  • Stub + linker pattern — inbound webhook creates the minimum entity; a triggered action does the heavy enrichment. Separation of concerns; enrichment is retryable and observable.
  • No multipart support — file uploads belong in a dedicated pipeline. If a provider sends files via webhook, pipe through an intermediary.
  • Inngest (features/inngest/) -- webhook-delivery function handles async delivery
  • Extraction (features/entities/extraction/) -- field actions can trigger webhooks
  • API Keys (features/api-keys/) -- external systems receiving webhooks may use API keys to call back into the platform
  • Admin -- webhook management lives in the Admin panel
  • Connections (features/agents/connection-presets.ts) -- REST connection presets used by actions that expand inbound webhook stubs

On this page