Docuseal Webhook Events: Available Types and Event Processing Pipeline
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 and related job files, the following events are available:
template.created— Triggered after a new template is persisted to the database via theafter_create_commitcallbacktemplate.updated— Fired when template attributes such as name, fields, or settings are modifiedtemplate.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 handles creation events, while sibling jobs manage other state transitions:
submission.created— Dispatched when a user completes and saves a form submissionsubmission.completed— Fired when all required fields are filled and the submission is finalized and lockedsubmission.expired— Triggered when a submission reaches its configured expiration date without completionsubmission.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 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 completingform.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, an after_create_commit callback triggers the delivery pipeline:
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, this service extracts relevant attributes and relationships into a standardized format that varies per webhook family.
All payloads share this core envelope structure:
{
"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. 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 constructs and dispatches POST requests. It cryptographically signs each payload using the stored secret:
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 and app/models/webhook_attempt.rb:
WebhookEvent— Aggregates all delivery attempts for a single logical event occurrenceWebhookAttempt— 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:
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 without interrupting webhook delivery.
Managing Webhooks via the UI
Docuseal provides administrative interfaces for webhook configuration and monitoring:
app/controllers/webhook_settings_controller.rb— Create, enable, disable, and list webhook URLs per accountapp/controllers/webhook_preferences_controller.rb— Configure user-level opt-in and opt-out preferences for specific webhook familiesapp/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 fromWebhookUrlrecords, HTTP delivery with HMAC-SHA256 signing viaSendWebhookRequest, persistence viaWebhookEventandWebhookAttemptmodels, and automatic retry with exponential backoff - Security relies on per-endpoint secrets stored in
WebhookUrlrecords and transmitted via theX-Docuseal-Signatureheader - 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 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.
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 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 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), 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), capturing every request, response, and timestamp for compliance and debugging purposes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →