Appearance
Outgoing webhooks
Outgoing webhooks notify your server when something happens in DocRunner. You register an endpoint URL, choose the events you want, and DocRunner sends a signed POST request for each one.
Event types
Events follow Resource.Action naming:
| Event | Fires when |
|---|---|
Packet.Created | A packet is created |
Packet.Sent | A packet is sent for signing |
Packet.Completed | All submitters have signed |
Packet.Voided | A packet is voided |
Packet.Archived | A packet is archived |
Packet.Restored | An archived packet is restored |
Submission.Opened | A submission is first opened |
Submission.Completed | A submission is fully signed |
Submitter.Invited | A submitter receives a signing invitation |
Submitter.Opened | A submitter opens the signing link |
Submitter.ConsentGiven | A submitter gives consent |
Submitter.Signed | A submitter completes signing |
Submitter.Declined | A submitter declines to sign |
PacketTemplate.Created | A packet template is created |
PacketTemplate.Updated | A packet template is updated |
PacketTemplate.Archived | A packet template is archived |
PacketTemplate.Restored | An archived packet template is restored |
Template.Created | A document template is created |
Template.Updated | A document template is updated |
Template.Archived | A document template is archived |
Template.Restored | An archived document template is restored |
Payload shape
Every delivery has the same envelope. The event-specific content is under data:
json
{
"event": "Packet.Completed",
"delivery_id": "8a7b6c5d-...",
"occurred_at": "2026-04-06T19:42:17Z",
"account_id": 7,
"data": { }
}The data contents depend on the event. There are five shapes:
| Shape | Events |
|---|---|
| Packet | Packet.Created, Packet.Sent, Packet.Completed, Packet.Voided, Packet.Archived, Packet.Restored |
| Submission | Submission.Opened, Submission.Completed |
| Submitter | Submitter.Invited, Submitter.Opened, Submitter.ConsentGiven, Submitter.Declined |
| Submitter Signed | Submitter.Signed |
| Template | PacketTemplate.Created, PacketTemplate.Updated, PacketTemplate.Archived, PacketTemplate.Restored, Template.Created, Template.Updated, Template.Archived, Template.Restored |
The packet object is identical everywhere it appears. Its metadata field is echoed unchanged from whatever was set when the packet was created. Use it as a join key to correlate events back to your system.
The submitter and each entry of submitters share one shape: id, slug, email, name, role, completed_at (ISO 8601, or null if not yet signed).
Packet events
data contains packet, submission, and submitters (every signer). submission is null and submitters is empty for a Packet.Created event on a packet that has no recipients yet.
json
{
"event": "Packet.Completed",
"data": {
"packet": {
"id": 123,
"slug": "abc123",
"status": "complete",
"packet_template_id": 9,
"metadata": { "pulseId": "123", "ownerId": "456" },
"sent_at": "2026-04-06T10:00:00Z",
"completed_at": "2026-04-06T19:42:17Z"
},
"submission": { "id": 88, "slug": "sub_abc123" },
"submitters": [
{
"id": 201,
"slug": "sign_abc123",
"email": "jane@example.com",
"name": "Jane Doe",
"role": "owner",
"completed_at": "2026-04-06T19:42:17Z"
}
]
}
}Submission events
data contains packet, submission (with status), and submitters. packet is null if the submission isn't part of a packet.
json
{
"event": "Submission.Opened",
"data": {
"packet": { "id": 123, "slug": "abc123", "status": "sent", "packet_template_id": 9, "metadata": { "pulseId": "123" }, "sent_at": "2026-04-06T10:00:00Z", "completed_at": null },
"submission": { "id": 88, "slug": "sub_abc123", "status": "viewed" },
"submitters": [
{ "id": 201, "slug": "sign_abc123", "email": "jane@example.com", "name": "Jane Doe", "role": "owner", "completed_at": null }
]
}
}Submission.Opened fires the first time any submitter opens the signing link. Submission.Completed fires when every submitter on the submission has signed.
Submitter events
data contains packet, submission, and a singular submitter identifying the signer that triggered the event. There is no submitters array.
json
{
"event": "Submitter.Declined",
"data": {
"packet": { "id": 123, "slug": "abc123", "status": "sent", "packet_template_id": 9, "metadata": { "pulseId": "123" }, "sent_at": "2026-04-06T10:00:00Z", "completed_at": null },
"submission": { "id": 88, "slug": "sub_abc123" },
"submitter": { "id": 201, "slug": "sign_abc123", "email": "jane@example.com", "name": "Jane Doe", "role": "owner", "completed_at": null }
}
}Submitter.Invited fires when a signer is sent their signing link (initially, or when the previous signing step completes). Submitter.Opened fires the first time that signer opens the link. Submitter.ConsentGiven fires when they accept the e-signature disclosure.
Submitter.Signed
Submitter.Signed has the same fields as the submitter events, plus the full submitters array. submitter tells you which signer just signed; submitters shows overall progress.
json
{
"event": "Submitter.Signed",
"data": {
"packet": { "id": 123, "slug": "abc123", "status": "awaiting_signatures", "packet_template_id": 9, "metadata": { "pulseId": "123" }, "sent_at": "2026-04-06T10:00:00Z", "completed_at": null },
"submission": { "id": 88, "slug": "sub_abc123" },
"submitter": { "id": 201, "slug": "sign_abc123", "email": "jane@example.com", "name": "Jane Doe", "role": "tenant", "completed_at": "2026-04-06T12:15:00Z" },
"submitters": [
{ "id": 201, "slug": "sign_abc123", "email": "jane@example.com", "name": "Jane Doe", "role": "tenant", "completed_at": "2026-04-06T12:15:00Z" },
{ "id": 202, "slug": "sign_def456", "email": "john@example.com", "name": "John Doe", "role": "owner", "completed_at": null }
]
}
}Template events
PacketTemplate.* events send data.packet_template; Template.* events send data.template. archived_at is null unless the template is archived.
json
{
"event": "PacketTemplate.Archived",
"data": {
"packet_template": {
"id": 9,
"slug": "lease_packet",
"name": "Lease Packet",
"archived_at": "2026-04-06T19:42:17Z"
}
}
}json
{
"event": "Template.Updated",
"data": {
"template": { "id": 4, "slug": "lease", "name": "Lease", "archived_at": null }
}
}Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-DocRunner-Event | Event type (e.g., Packet.Completed) |
X-DocRunner-Delivery | Unique delivery ID for deduplication |
X-DocRunner-Signature | sha256=<hex> HMAC-SHA256 signature |
User-Agent | DocRunner-Webhooks/1.0 |
Signature verification
The X-DocRunner-Signature header contains an HMAC-SHA256 hex digest of the raw request body, signed with your endpoint's secret.
Ruby
ruby
def verify_signature(request_body, signature_header, secret)
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, request_body)
Rack::Utils.secure_compare(expected, signature_header)
endNode.js
javascript
const crypto = require('crypto');
function verifySignature(body, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}Retries
Failed deliveries (non-2xx responses or timeouts) are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | 30 seconds |
| 2 | 2 minutes |
| 3 | 10 minutes |
| 4 | 1 hour |
| 5 | 6 hours |
After 5 failed attempts, the delivery is marked as failed and a notification email is sent to the endpoint's contact email.
Managing endpoints
Webhook endpoints are created, edited, tested and redelivered in the DocRunner app at Settings > Developer Tools > Webhooks. There is no API for managing endpoints.