Skip to content

Webhooks

Webhooks let you receive HTTP POST requests to your server when events happen in VisiSign — such as a document being signed or completed. Use them to trigger downstream automations, send notifications, or sync data with other systems.

  1. Go to Settings > Webhooks in VisiSign.
  2. Enter the HTTPS URL that will receive events.
  3. Select which events to subscribe to (or leave all selected).
  4. Click Add Endpoint. The signing secret is shown once — copy it immediately.
EventFires when
signature_request.sentA signature request is sent to signers
signature_request.viewedA signer opens the signing link
signature_request.signedA signer completes their signature
signature_request.declinedA signer declines to sign
signature_request.completedAll signers have signed
signature_request.expiredThe request reaches its expiration date
signature_request.cancelledThe request is cancelled by the sender

Subscribe to all events by leaving the events array empty, or select specific ones.

Every webhook delivery is an HTTP POST with a JSON body:

{
"event": {
"type": "signature_request.signed",
"timestamp": "2026-03-04T12:00:00Z"
},
"signature_request": {
"id": "sr_123",
"title": "Service Agreement",
"status": "sent",
"form_data": {},
"signers": [
{
"id": "sig_456",
"name": "Jane Smith",
"email": "jane@example.com",
"status": "signed",
"signed_at": "2026-03-04T12:00:00Z"
}
]
}
}

When fields have a mapping_key set (either via the API or the template builder), VisiSign extracts the values signers entered and includes them in the form_data object. This is useful for structured forms like W-9s, onboarding documents, or any form where you need the field values programmatically.

For example, a completed W-9 webhook might include:

{
"signature_request": {
"status": "completed",
"form_data": {
"taxpayer_name": "Jane Smith",
"tin": "123-45-6789",
"address": "123 Main St, Springfield, IL 62704",
"tax_classification": "Individual"
},
"files_url": "/v1/signature_requests/sr_123/files"
}
}

form_data is populated when the signature_request.completed event fires. For other events (sent, viewed, signed), it will be an empty object {}.

Each webhook endpoint has a signing secret (prefixed whsec_). Use it to verify that deliveries are genuinely from VisiSign by checking the X-VisiSign-Signature header.

The header carries a Stripe-style comma-separated list of key/value pairs:

X-VisiSign-Signature: t=1734567890,v1=2c1e85f4...0d
  • t is the Unix timestamp (seconds) at which the delivery was signed.
  • v1 is the lowercase hex HMAC-SHA256 of the string <t>.<raw_body> using your signing secret as the key.

To verify, recompute HMAC_SHA256(secret, "<t>.<raw_body>") and constant-time compare against v1. Reject deliveries where t is older than ~5 minutes to defend against replay attacks.

import hmac, hashlib, time
def verify_signature(raw_body: bytes, header: str, secret: str, max_age: int = 300) -> bool:
parts = dict(kv.split("=", 1) for kv in header.split(","))
t, received = parts.get("t"), parts.get("v1")
if not t or not received:
return False
if abs(time.time() - int(t)) > max_age:
return False
expected = hmac.new(
secret.encode(),
f"{t}.".encode() + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, received)
parts = header.split(",").map { |kv| kv.split("=", 2) }.to_h
t, received = parts["t"], parts["v1"]
return false unless t && received
return false if (Time.now.to_i - t.to_i).abs > 300
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}")
Rack::Utils.secure_compare(expected, received)
import { createHmac, timingSafeEqual } from "crypto";
function verifySignature(rawBody, header, secret, maxAgeSeconds = 300) {
const parts = Object.fromEntries(
header.split(",").map((kv) => {
const i = kv.indexOf("=");
return [kv.slice(0, i).trim(), kv.slice(i + 1).trim()];
}),
);
const t = parts.t, received = parts.v1;
if (!t || !received) return false;
if (Math.abs(Date.now() / 1000 - Number(t)) > maxAgeSeconds) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(received);
return a.length === b.length && timingSafeEqual(a, b);
}

Send a test event from the VisiSign dashboard by clicking Test next to any webhook endpoint. This sends a delivery with a test payload so you can verify your endpoint is receiving and processing events correctly.

Endpoints that fail to return a 2xx response accumulate consecutive failures. After 10 consecutive failures, the endpoint is automatically disabled. Fix your endpoint and re-create it in Settings.

Each delivery includes these headers:

HeaderDescription
Content-Typeapplication/json
User-AgentVisiSign-Webhooks/1.0
X-VisiSign-EventThe event type (e.g. signature_request.signed)
X-VisiSign-SignatureStripe-style t=<unix_ts>,v1=<hex> HMAC-SHA256 over <t>.<raw_body> (see Verifying signatures)