Skip to content

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:

EventFires when
Packet.CreatedA packet is created
Packet.SentA packet is sent for signing
Packet.CompletedAll submitters have signed
Packet.VoidedA packet is voided
Packet.ArchivedA packet is archived
Packet.RestoredAn archived packet is restored
Submission.OpenedA submission is first opened
Submission.CompletedA submission is fully signed
Submitter.InvitedA submitter receives a signing invitation
Submitter.OpenedA submitter opens the signing link
Submitter.ConsentGivenA submitter gives consent
Submitter.SignedA submitter completes signing
Submitter.DeclinedA submitter declines to sign
PacketTemplate.CreatedA packet template is created
PacketTemplate.UpdatedA packet template is updated
PacketTemplate.ArchivedA packet template is archived
PacketTemplate.RestoredAn archived packet template is restored
Template.CreatedA document template is created
Template.UpdatedA document template is updated
Template.ArchivedA document template is archived
Template.RestoredAn 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:

ShapeEvents
PacketPacket.Created, Packet.Sent, Packet.Completed, Packet.Voided, Packet.Archived, Packet.Restored
SubmissionSubmission.Opened, Submission.Completed
SubmitterSubmitter.Invited, Submitter.Opened, Submitter.ConsentGiven, Submitter.Declined
Submitter SignedSubmitter.Signed
TemplatePacketTemplate.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 ​

HeaderValue
Content-Typeapplication/json
X-DocRunner-EventEvent type (e.g., Packet.Completed)
X-DocRunner-DeliveryUnique delivery ID for deduplication
X-DocRunner-Signaturesha256=<hex> HMAC-SHA256 signature
User-AgentDocRunner-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)
end

Node.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:

AttemptDelay
130 seconds
22 minutes
310 minutes
41 hour
56 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.

Questions? Email hello@docrunner.io