# Docuseal Webhook Events: Available Types and Event Processing Pipeline

> Explore Docuseal webhook events for Template, Submission, and Form. Understand how our deterministic pipeline processes webhook events for document lifecycle changes.

- Repository: [DocuSeal/docuseal](https://github.com/docusealco/docuseal)
- Tags: api-reference
- Published: 2026-05-05

---

**Docuseal exposes three webhook families—Template, Submission, and Form—that deliver HMAC-signed JSON payloads via a deterministic, retry-aware background job pipeline whenever specific document lifecycle changes occur.**

Docuseal implements a robust webhook system that notifies external systems about template modifications, form submissions, and user interactions. The platform organizes these events into three distinct families, each processed through a standardized six-stage pipeline defined in the `docusealco/docuseal` repository. Understanding these available webhooks and their event processing architecture is essential for building secure, reliable integrations.

## Available Webhook Families and Events

Docuseal organizes webhook events into three families based on the underlying domain model. Each family fires specific events implemented as dedicated background jobs.

### Template Events

Template webhooks fire when document templates change state. According to [`app/jobs/send_template_created_webhook_request_job.rb`](https://github.com/docusealco/docuseal/blob/main/app/jobs/send_template_created_webhook_request_job.rb) and related job files, the following events are available:

- **`template.created`** — Triggered after a new template is persisted to the database via the `after_create_commit` callback
- **`template.updated`** — Fired when template attributes such as name, fields, or settings are modified
- **`template.archived`** — Occurs when a template is soft-deleted or moved to archived status

### Submission Events

Submission events track the lifecycle of filled and signed documents. The [`app/jobs/send_submission_created_webhook_request_job.rb`](https://github.com/docusealco/docuseal/blob/main/app/jobs/send_submission_created_webhook_request_job.rb) handles creation events, while sibling jobs manage other state transitions:

- **`submission.created`** — Dispatched when a user completes and saves a form submission
- **`submission.completed`** — Fired when all required fields are filled and the submission is finalized and locked
- **`submission.expired`** — Triggered when a submission reaches its configured expiration date without completion
- **`submission.archived`** — Occurs when a submission is archived after signing or administrative action

### Form Events

Form events capture user interactions with the document filling interface. Jobs such as [`app/jobs/send_form_viewed_webhook_request_job.rb`](https://github.com/docusealco/docuseal/blob/main/app/jobs/send_form_viewed_webhook_request_job.rb) process these interaction events:

- **`form.viewed`** — Triggered when a form URL is loaded (initial page view)
- **`form.started`** — Fired when the user begins interacting with form fields (first input)
- **`form.declined`** — Occurs when a user explicitly declines or abandons the form without completing
- **`form.completed`** — Dispatched when the user successfully finishes the form flow, which may or may not create a submission

## How Webhook Events Are Processed

The platform processes webhook events through a deterministic six-stage pipeline implemented across models, jobs, and service classes.

### 1. Event Detection and Job Enqueuing

Model callback hooks detect state changes and enqueue specific background jobs. In [`app/models/submission.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/submission.rb), an `after_create_commit` callback triggers the delivery pipeline:

```ruby
after_create_commit do
  SendSubmissionCreatedWebhookRequestJob.perform_later(id)
end

```

Each event type maintains an isolated job class—such as `SendTemplateCreatedWebhookRequestJob` and `SendFormViewedWebhookRequestJob`—ensuring independent processing queues and failure domains.

### 2. Payload Serialization

The `Submitters::SerializeForWebhook` service class transforms model instances into JSON structures. Located in [`lib/submitters/serialize_for_webhook.rb`](https://github.com/docusealco/docuseal/blob/main/lib/submitters/serialize_for_webhook.rb), this service extracts relevant attributes and relationships into a standardized format that varies per webhook family.

All payloads share this core envelope structure:

```json
{
  "event": "submission.created",
  "account_id": 42,
  "payload": { }
}

```

The `payload` object contains serialized model data specific to the event type, with detailed schemas documented in the `docs/webhooks/` directory.

### 3. URL Resolution and Secret Retrieval

For each event, the system queries active `WebhookUrl` records from [`app/models/webhook_url.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/webhook_url.rb). Each row stores:

- The target HTTPS endpoint URL
- A unique secret string for HMAC signature generation
- Account associations for multi-tenant scoping

The job iterates over all active URLs for the relevant account, preparing individual signed requests for each endpoint.

### 4. HTTP Delivery and HMAC Signing

The `SendWebhookRequest` module in [`lib/send_webhook_request.rb`](https://github.com/docusealco/docuseal/blob/main/lib/send_webhook_request.rb) constructs and dispatches POST requests. It cryptographically signs each payload using the stored secret:

```ruby
def self.call(url:, secret:, event:, payload:, attempt:)
  body = { event: event, account_id: payload[:account_id], payload: payload }.to_json
  signature = OpenSSL::HMAC.hexdigest('SHA256', secret, body)

  response = Faraday.post(url) do |req|
    req.headers['Content-Type'] = 'application/json'
    req.headers['X-Docuseal-Signature'] = signature
    req.body = body
  end
  
  attempt.update(status: response.status, response_body: response.body)
  response.success?
rescue => e
  attempt.update(status: 0, error_message: e.message)
  false
end

```

This generates an `X-Docuseal-Signature` header containing an HMAC-SHA256 hash, ensuring payload integrity and authenticity verification on the receiver side.

### 5. Event Logging and Retry Logic

The system persists delivery attempts using two models defined in [`app/models/webhook_event.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/webhook_event.rb) and [`app/models/webhook_attempt.rb`](https://github.com/docusealco/docuseal/blob/main/app/models/webhook_attempt.rb):

- **`WebhookEvent`** — Aggregates all delivery attempts for a single logical event occurrence
- **`WebhookAttempt`** — Records individual HTTP round-trips, including response status codes, bodies, and error messages

When a request returns a non-2xx status or encounters a network error, the job re-queues itself with exponential backoff. Each retry creates a new `WebhookAttempt` record linked to the original `WebhookEvent`, creating a complete audit trail for debugging and compliance.

## Security and Signature Verification

Each `WebhookUrl` stores a configurable secret used to sign payloads. Receivers must verify the `X-Docuseal-Signature` header to confirm the request originated from Docuseal and was not tampered with in transit:

```ruby
require 'openssl'

def valid_signature?(request_body, signature_header, secret)
  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, request_body)
  Rack::Utils.secure_compare(expected, signature_header)
end

```

Secrets can be rotated through the Webhook Secret management interface implemented in [`app/controllers/webhook_secret_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/webhook_secret_controller.rb) without interrupting webhook delivery.

## Managing Webhooks via the UI

Docuseal provides administrative interfaces for webhook configuration and monitoring:

- **[`app/controllers/webhook_settings_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/webhook_settings_controller.rb)** — Create, enable, disable, and list webhook URLs per account
- **[`app/controllers/webhook_preferences_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/webhook_preferences_controller.rb)** — Configure user-level opt-in and opt-out preferences for specific webhook families
- **[`app/controllers/webhook_events_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/webhook_events_controller.rb)** — Read-only audit view of recent events and delivery attempts with response details

## Summary

- Docuseal provides **three webhook families**: Template events (created, updated, archived), Submission events (created, completed, expired, archived), and Form events (viewed, started, declined, completed)
- Event processing follows a **six-stage pipeline**: detection via model callbacks, payload serialization via `Submitters::SerializeForWebhook`, URL resolution from `WebhookUrl` records, HTTP delivery with HMAC-SHA256 signing via `SendWebhookRequest`, persistence via `WebhookEvent` and `WebhookAttempt` models, and automatic retry with exponential backoff
- **Security** relies on per-endpoint secrets stored in `WebhookUrl` records and transmitted via the `X-Docuseal-Signature` header
- **Audit trails** are maintained through relational records tracking every delivery attempt, response code, and error message
- Each event type utilizes a **dedicated background job** (e.g., `SendSubmissionCreatedWebhookRequestJob`) ensuring isolated, queue-based processing

## Frequently Asked Questions

### What is the retry policy for failed webhook deliveries?

Docuseal implements exponential backoff for failed webhook attempts. When `SendWebhookRequest` in [`lib/send_webhook_request.rb`](https://github.com/docusealco/docuseal/blob/main/lib/send_webhook_request.rb) encounters a non-2xx response or network exception, the job re-queues itself up to a configured retry limit. Each attempt creates a new `WebhookAttempt` record linked to the parent `WebhookEvent`, providing full visibility into retry history through the database schema defined in [`db/migrate/20250727130628_create_webhook_events_and_attempts.rb`](https://github.com/docusealco/docuseal/blob/main/db/migrate/20250727130628_create_webhook_events_and_attempts.rb).

### How do I verify that webhook requests came from Docuseal?

Verify the `X-Docuseal-Signature` header against an HMAC-SHA256 hash of the raw request body using your stored webhook secret. The signature is generated in [`lib/send_webhook_request.rb`](https://github.com/docusealco/docuseal/blob/main/lib/send_webhook_request.rb) using `OpenSSL::HMAC.hexdigest('SHA256', secret, body)`, and must be validated using a constant-time comparison function like `Rack::Utils.secure_compare` to prevent timing attacks. The secret is stored per-endpoint in the `webhook_urls` database table.

### Can I subscribe to specific webhook events rather than all events?

Yes. While each configured URL receives all events for its enabled families, the [`app/controllers/webhook_preferences_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/webhook_preferences_controller.rb) interface allows account administrators to configure which webhook families (Template, Submission, Form) are active. Additionally, your receiving endpoint should filter by the `event` field in the JSON payload, which contains specific values like `template.created` or `submission.completed` to route processing logic accordingly.

### Where are webhook configurations stored in the database?

Webhook endpoint configurations persist in the `webhook_urls` table (created via [`db/migrate/20250714172222_create_webhook_urls.rb`](https://github.com/docusealco/docuseal/blob/main/db/migrate/20250714172222_create_webhook_urls.rb)), which stores HTTPS URLs and HMAC secrets. Delivery audit logs are maintained in the `webhook_events` and `webhook_attempts` tables (created via [`db/migrate/20250727130628_create_webhook_events_and_attempts.rb`](https://github.com/docusealco/docuseal/blob/main/db/migrate/20250727130628_create_webhook_events_and_attempts.rb)), capturing every request, response, and timestamp for compliance and debugging purposes.