django-forms-workflows includes a first-class outbound webhook system for workflow lifecycle events. Use webhooks when another system needs a real-time signal that a submission was created, approved, rejected, returned for correction, or assigned to a reviewer.
Unlike PostSubmissionAction(action_type="api"), webhook endpoints are:
- attached directly to a
WorkflowDefinition - subscribed to specific workflow events
- delivered asynchronously via Celery tasks (with synchronous fallback)
- signed with an HMAC secret for receiver-side verification
- logged per attempt in
WebhookDeliveryLog
One outbound subscription for one workflow track.
Key fields:
workflow— workflow that owns the webhookname— admin-friendly labelurl— destination URLsecret— HMAC signing secret; auto-generated if blankevents— list of subscribed event keyscustom_headers— optional static JSON headersis_active— toggle delivery on/off without deleting configtimeout_seconds— HTTP timeout per attemptretry_on_failure/max_retries— retry behavior for non-2xx or transport failures
Stores an audit record for each delivery attempt, including:
- event name
- endpoint name and URL
- related workflow / submission / approval task
- request headers and payload
- HTTP status code
- response body or exception text
- attempt number and timestamp
| Event | Fires when |
|---|---|
submission.created |
A submission enters the workflow engine |
task.created |
A new approval task is created for a workflow stage |
submission.approved |
The submission reaches final approval |
submission.rejected |
The submission is finally rejected |
submission.returned |
A send-back action returns the submission to an earlier stage |
Configure webhook endpoints in Django Admin:
- Open the target form and workflow track.
- In the Webhook Endpoints inline, add a new endpoint.
- Enter the destination
url. - Select one or more subscribed
events. - Optionally add
custom_headersas JSON. - Leave
secretblank to auto-generate one, or paste your own shared secret. - Save the workflow.
You can also manage endpoints directly from Admin → Webhook Endpoints and inspect deliveries in Admin → Webhook Delivery Logs.
- Deliveries are sent as
POSTrequests withContent-Type: application/json. - The request body is canonical JSON produced from the workflow event payload.
- A 2xx response is considered success.
- Non-2xx responses and transport exceptions are logged as failures.
- If retries are enabled, the system schedules retries with backoff up to
max_retries.
Every request includes:
Content-Type: application/jsonUser-Agent: django-forms-workflows/webhooksX-Forms-Workflows-Event: <event>X-Forms-Workflows-Signature: sha256=<hex digest>
Any custom_headers configured on the endpoint are merged into the outbound request.
The signature is computed as:
- algorithm: HMAC-SHA256
- key:
WebhookEndpoint.secret - message: raw JSON request body
Receiver flow:
- read the raw request body exactly as sent
- compute
HMAC_SHA256(secret, raw_body) - compare it to
X-Forms-Workflows-Signature - reject the request if they do not match
Payloads share a consistent top-level structure:
eventtimestampsubmissionformsubmitterworkflowtasktarget_stage
Notes:
taskis populated fortask.createdand send-back events tied to a specific approval task.target_stageis populated forsubmission.returnedso receivers know where the submission was sent back.workflowidentifies the specific workflow track that emitted the event.
{
"event": "task.created",
"timestamp": "2026-04-01T06:20:00.000000+00:00",
"submission": {"id": 42, "status": "pending_approval"},
"form": {"id": 7, "name": "Travel Request", "slug": "travel-request"},
"submitter": {"id": 3, "username": "alice", "email": "alice@example.com"},
"workflow": {"id": 5, "name_label": "Finance Approval", "requires_approval": true},
"task": {"id": 88, "status": "pending", "stage_number": 2, "step_name": "Finance Review"},
"target_stage": null
}Additional fields such as created_at, submitted_at, completed_at, assigned user/group, workflow-stage metadata, and approving user are included when available.
Use workflow webhooks when you need:
- one reusable event subscription per workflow
- standardized signed payloads
- event selection like
task.createdorsubmission.returned - delivery audit logs in a dedicated model
Use post-submission API actions when you need:
- a one-off API call tied to
on_submit,on_approve,on_reject, oron_complete - templated request bodies specific to one integration
- ordered execution alongside database / LDAP / custom actions
- Cloning a form/workflow in admin also clones its webhook endpoints.
- Form sync export/import includes webhook endpoint definitions.