Send documents for signature, track them, download the certified PDF, and receive signed completion events, all over a REST API. Available on the Business plan.
Authenticate every request with your API key in the X-DenSign-Key header, or as an ?api_key= query parameter. Treat the key like a password.
curl https://sign.dendat.ai/api/v1/documents \
-H "X-DenSign-Key: YOUR_API_KEY"
401 Unauthorized. The response does not distinguish between the two.
https://sign.dendat.ai
Upload a PDF and one or more signers. Fields are detected on upload, each signer is emailed a secure link, and your webhook is called on completion if you registered one. Encoding is multipart/form-data.
| Field | Required | Description |
|---|---|---|
file | yes | The PDF to be signed |
title | no | Document title, defaults to the filename |
signer_name | yes | First signer's name |
signer_email | yes | First signer's email |
message | no | Note included in the signing email |
webhook_url | no | URL to POST when the document completes |
curl -X POST https://sign.dendat.ai/api/v1/send \
-H "X-DenSign-Key: YOUR_API_KEY" \
-F "[email protected]" \
-F "title=Service Agreement" \
-F "signer_name=Jane Doe" \
-F "[email protected]" \
-F "webhook_url=https://yourapp.com/hooks/densign"
{
"success": true,
"document_id": 123,
"status": "sent",
"signing_url": "https://sign.dendat.ai/sign/<token>"
}
Returns the document, its status, its signers and its fields. The field map alone is available at GET /api/v1/document/{id}/fields.
Returns the completed, certified PDF with the certificate page appended. Available once status = completed.
The full timestamped log for a document: viewed, signed, declined, completed and every reminder sent. This is the record you produce if a signature is ever questioned.
Paginated list of your documents, filterable by status.
Send the same document to many recipients in one call. To nudge everyone still outstanding on a document, use POST /api/v1/document/{id}/bulk-remind.
Set webhook_url when sending. Once every signer completes, DENSIGN posts a JSON event to your URL:
POST https://yourapp.com/hooks/densign
X-DenSign-Event: document.completed
X-DenSign-Signature: t=1720080000,v1=<hmac-sha256-hex>
{
"id": "evt_...",
"event": "document.completed",
"created": 1720080000,
"document_id": 123,
"title": "Service Agreement",
"status": "completed",
"completed_at": "2026-07-04T09:00:00",
"signed_file_hash": "3f9a2c...",
"certificate_url": "https://sign.dendat.ai/verify?hash=3f9a2c...",
"signers": [{ "name": "Jane Doe", "email": "[email protected]", "status": "signed", "signed_at": "..." }]
}
Respond 2xx to acknowledge. A failed delivery is retried once.
Every webhook is signed so you can confirm it came from DENSIGN and was not tampered with in transit. The X-DenSign-Signature header is t=<timestamp>,v1=<signature>, where the signature is HMAC-SHA256 over "{timestamp}.{raw_body}" using your webhook signing secret.
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(),
f'{parts["t"]}.{raw_body.decode()}'.encode(),
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
const crypto = require("crypto");
function verify(rawBody, header, secret) {
const p = Object.fromEntries(header.split(",").map(s => s.split("=")));
const expected = crypto.createHmac("sha256", secret)
.update(`${p.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}
Anyone holding a signed DENSIGN PDF can confirm it is authentic and unaltered with no account, at https://sign.dendat.ai/verify, by entering the SHA-256 content hash printed on the certificate page. Lookup is by hash only, so the endpoint cannot be walked to discover documents.