SignSecureSignSecure Docs

Webhooks

Receive real-time HTTP notifications when document events occur in your account.

View Markdown

Webhooks let your application receive real-time POST requests whenever something happens to your documents — created, sent, signed, completed, and more.

Setting Up a Webhook

  1. Go to Settings > Webhooks
  2. Click Add Webhook
  3. Enter your endpoint URL (must be HTTP or HTTPS)
  4. Select the events you want to subscribe to
  5. Save — a signing secret (whsec_...) is generated and shown once. Copy it immediately.

Event Types

EventDescription
*Subscribe to all events
document.createdA new document was created
document.updatedA document's title or metadata was updated
document.deletedA document was deleted
document.sentA document was sent to recipients for signing
document.viewedA recipient opened the signing page
document.signedA recipient signed or approved the document
document.completedAll recipients have signed — document is fully complete

HTTP Request Format

Every webhook delivery is a POST request to your endpoint URL with a JSON body.

Headers

HeaderDescription
Content-Typeapplication/json
User-AgentSignSecure-Webhooks/1.0
X-SignSecure-EventThe event type, e.g. document.signed
X-SignSecure-DeliveryUnique delivery ID, e.g. evt_abc123
X-SignSecure-SignatureSignature for verification (see below)

Envelope

Every webhook payload has the same top-level structure:

{
  "id": "evt_abc123",
  "event": "document.signed",
  "createdAt": "2026-03-11T10:30:00.000Z",
  "data": {
    // event-specific fields — see below
  }
}
FieldTypeDescription
idstringUnique event ID (evt_ prefix)
eventstringThe event type
createdAtstringISO 8601 timestamp
dataobjectEvent-specific payload

Event Payloads

document.created

Fired when a new document is created.

{
  "id": "evt_abc123",
  "event": "document.created",
  "createdAt": "2026-03-11T10:30:00.000Z",
  "data": {
    "documentId": "doc_xyz789",
    "title": "Employment Agreement",
    "fileName": "employment-agreement.pdf",
    "status": "draft",
    "createdAt": "2026-03-11T10:30:00.000Z",
    "recipientsCount": 2
  }
}

document.updated

Fired when a document's title or metadata changes.

{
  "id": "evt_abc123",
  "event": "document.updated",
  "createdAt": "2026-03-11T10:35:00.000Z",
  "data": {
    "documentId": "doc_xyz789",
    "title": "Employment Agreement v2",
    "status": "draft",
    "updatedAt": "2026-03-11T10:35:00.000Z",
    "changes": {
      "title": "Employment Agreement v2"
    }
  }
}

document.sent

Fired when a document is sent to recipients for signing.

{
  "id": "evt_abc123",
  "event": "document.sent",
  "createdAt": "2026-03-11T11:00:00.000Z",
  "data": {
    "documentId": "doc_xyz789",
    "title": "Employment Agreement",
    "sentAt": "2026-03-11T11:00:00.000Z",
    "recipients": [
      {
        "name": "Jane Doe",
        "email": "jane@example.com",
        "role": "signer"
      },
      {
        "name": "Bob Smith",
        "email": "bob@example.com",
        "role": "approver"
      }
    ]
  }
}

document.viewed

Fired when a recipient opens the document signing page.

{
  "id": "evt_abc123",
  "event": "document.viewed",
  "createdAt": "2026-03-11T11:15:00.000Z",
  "data": {
    "documentId": "doc_xyz789",
    "title": "Employment Agreement",
    "status": "pending",
    "viewedBy": {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "role": "signer"
    },
    "viewedAt": "2026-03-11T11:15:00.000Z"
  }
}

document.signed

Fired when a recipient signs or approves the document.

{
  "id": "evt_abc123",
  "event": "document.signed",
  "createdAt": "2026-03-11T11:20:00.000Z",
  "data": {
    "documentId": "doc_xyz789",
    "title": "Employment Agreement",
    "signedBy": {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "signatureMethod": "electronic",
      "actionType": "signed"
    },
    "signedAt": "2026-03-11T11:20:00.000Z",
    "remainingRecipients": 1
  }
}

signatureMethod is one of: electronic, aadhaar_otp, dsc_usb.

actionType is either signed or approved.

document.completed

Fired when all recipients have signed and the document is fully complete.

{
  "id": "evt_abc123",
  "event": "document.completed",
  "createdAt": "2026-03-11T11:30:00.000Z",
  "data": {
    "documentId": "doc_xyz789",
    "title": "Employment Agreement",
    "completedAt": "2026-03-11T11:30:00.000Z",
    "recipients": [
      {
        "name": "Jane Doe",
        "email": "jane@example.com",
        "signedAt": "2026-03-11T11:20:00.000Z"
      },
      {
        "name": "Bob Smith",
        "email": "bob@example.com",
        "signedAt": "2026-03-11T11:28:00.000Z"
      }
    ]
  }
}

Verifying Signatures

Every webhook request includes an X-SignSecure-Signature header. Use it to verify the request came from SignSecure and wasn't tampered with.

The header format is:

t=1710150600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t — Unix timestamp (seconds) when the signature was generated
  • v1 — HMAC-SHA256 hex digest

Verification Steps

  1. Extract t and v1 from the header
  2. Construct the signed payload: {t}.{raw_json_body}
  3. Compute HMAC-SHA256 of the signed payload using your webhook secret
  4. Compare the computed signature with v1

Node.js Example

import crypto from "node:crypto";

function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("="))
  );
  const timestamp = parts.t;
  const signature = parts.v1;

  const signedPayload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signedPayload)
    .digest("hex");

  if (expected !== signature) {
    throw new Error("Invalid webhook signature");
  }

  // Optional: reject if timestamp is too old (e.g. > 5 minutes)
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
  if (age > 300) {
    throw new Error("Webhook timestamp too old");
  }

  return true;
}

Python Example

import hmac
import hashlib
import time

def verify_webhook_signature(raw_body: str, signature_header: str, secret: str):
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    timestamp = parts["t"]
    signature = parts["v1"]

    signed_payload = f"{timestamp}.{raw_body}"
    expected = hmac.new(
        secret.encode(), signed_payload.encode(), hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        raise ValueError("Invalid webhook signature")

    # Optional: reject stale timestamps
    age = int(time.time()) - int(timestamp)
    if age > 300:
        raise ValueError("Webhook timestamp too old")

    return True

Retry Behavior

If your endpoint doesn't respond with a 2xx status code (or times out after 30 seconds), SignSecure retries the delivery:

AttemptDelay
1stImmediate
2nd1 minute later
3rd10 minutes later

After 3 failed attempts, the delivery is marked as permanently failed. You can manually retry failed deliveries from the webhook detail page in Settings > Webhooks.

Best Practices

  • Respond quickly — return a 200 status code as fast as possible. Process the event asynchronously if needed.
  • Verify signatures — always validate the X-SignSecure-Signature header before trusting the payload.
  • Handle duplicates — use the id field to deduplicate events in case of retries.
  • Use HTTPS — always use an HTTPS endpoint in production.
  • Monitor deliveries — check the webhook delivery log in Settings to catch failures early.

On this page