SecuSign API Docs
Use the SecuSign API to create envelopes, upload PDFs, place signing fields, send documents for OTP-secured electronic signing, and retrieve the completed final PDF plus ledger evidence after signing.
Current API scope
The first public API version supports the core signing flow:
- Create a draft envelope
- Upload or replace a PDF
- Set per-recipient field placements
- Send the envelope for signing
- List and read envelope status
- Retrieve completed final PDFs, ledger PDFs, and ledger JSON
Authentication
Create an API key in Account → API Keys. The full key is shown once. Store it securely.
Authorization: Bearer ss_test_xxxxxxxxxxxxxxxxx
SecuSign stores only a protected hash of the API key. Revoked keys stop working immediately.
Base URL
Use the production API endpoint for live envelopes:
https://secusign.app
All API requests use HTTPS and Bearer-token authentication.
Connector demo download
SecuSign includes a customer-hostable connector demo for workflow automation.
The connector receives signed SecuSign webhooks, verifies the HMAC signature,
waits for envelope.completed, then retrieves the final PDF,
ledger PDF, ledger JSON, envelope metadata, and verification URL through the API.
Use it as a reference foundation for OnlyOffice, Asana, Monday.com, CRM systems, file servers, or internal document platforms. Business plan is required for webhook-driven automation. Personal plan users can still use the API to poll envelope status and retrieve completed artifacts.
The connector calls https://secusign.app by default. Your webhook URL,
however, must be your own public HTTPS endpoint that forwards to the connector
running on your server.
tar -xzf secusign-connector-demo.tar.gz
cd secusign_connector_demo
cp .env.example .env
python3 -m pip install -r requirements.txt
python3 -m uvicorn connector:app --host 127.0.0.1 --port 8011
Do not commit real API keys or webhook secrets. The connector package includes
only .env.example; runtime output and local secrets are excluded.
Quick start: full envelope flow
Create draft envelope
curl -s -X POST "$BASE_URL/api/v1/envelopes" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"client_token": "my-system-unique-id-001",
"title": "API Contract Test",
"message": "Please review and sign.",
"expires_at": "2026-05-30",
"signature_expiry": "2026-06-30",
"recipients": [
{
"name": "Signer Name",
"email": "[email protected]",
"role": "signer",
"stage": 1,
"stamp_mode": "none"
}
]
}
JSON
Upload PDF
curl -s -X POST "$BASE_URL/api/v1/envelopes/$EID/document" \
-H "Authorization: Bearer $API_KEY" \
-F "[email protected];type=application/pdf"
Set field placements
Page numbers are zero-based. Coordinates are percentages of page width/height.
curl -s -X PUT "$BASE_URL/api/v1/envelopes/$EID/fields" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"fields_by_recipient": {
"[email protected]": {
"sig": {
"0": [
{ "x_pct": 42, "y_pct": 66, "w_mm": 45 }
]
},
"name": {
"0": [
{ "x_pct": 42, "y_pct": 74, "font_pt": 10 }
]
},
"date": {
"0": [
{ "x_pct": 42, "y_pct": 78, "font_pt": 10 }
]
}
}
}
}
JSON
Send envelope
curl -s -X POST "$BASE_URL/api/v1/envelopes/$EID/send" \
-H "Authorization: Bearer $API_KEY"
Read envelope status
Poll the envelope or list envelopes until status is completed.
The response includes an artifacts block with the verification URL,
final PDF hash, ledger PDF hash, and download paths when available.
curl -s "$BASE_URL/api/v1/envelopes/$EID" \
-H "Authorization: Bearer $API_KEY"
curl -s "$BASE_URL/api/v1/envelopes?status=completed&limit=25" \
-H "Authorization: Bearer $API_KEY"
Retrieve final PDF and ledger evidence
After completion, API clients can fetch the same final executed PDF, ledger PDF, and machine-readable ledger evidence that normal UI-created envelopes receive. Signing still happens through the secure signer page with OTP and audit events.
# Machine-readable evidence
curl -s "$BASE_URL/api/v1/envelopes/$EID/ledger" \
-H "Authorization: Bearer $API_KEY"
# Final executed PDF
curl -L "$BASE_URL/api/v1/envelopes/$EID/final.pdf" \
-H "Authorization: Bearer $API_KEY" \
-o final.pdf
# Ledger PDF
curl -L "$BASE_URL/api/v1/envelopes/$EID/ledger.pdf" \
-H "Authorization: Bearer $API_KEY" \
-o ledger.pdf
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/me | Verify API key and account plan. |
POST | /api/v1/envelopes | Create a draft envelope. |
GET | /api/v1/envelopes | List envelopes owned by the API user. Supports status, limit, and offset. |
GET | /api/v1/envelopes/{eid} | Read envelope status, metadata, and artifact availability. |
POST | /api/v1/envelopes/{eid}/document | Upload or replace PDF while draft. |
PUT | /api/v1/envelopes/{eid}/fields | Set signing field placements. |
POST | /api/v1/envelopes/{eid}/send | Send/start signing flow. |
GET | /api/v1/envelopes/{eid}/ledger | Return machine-readable ledger evidence for a completed envelope. |
GET | /api/v1/envelopes/{eid}/final.pdf | Download the final executed PDF after completion. |
GET | /api/v1/envelopes/{eid}/ledger.pdf | Download the ledger PDF after completion. |
Recipient stamp modes
| Mode | Meaning |
|---|---|
none | No company stamp required for this signer. |
sender | Use the sender/account company stamp. |
upload_optional | Signer may upload their own company stamp. |
upload_required | Signer must upload their company stamp before signing. |
Planned next
- Templates API
- Bulk send API
- Embedded signing options
- Integration connectors for document/workflow platforms
- Durable webhook queue hardening for higher-volume production use
Webhooks
Webhooks allow Business accounts to receive signed event notifications when an envelope is sent,
signed, refused, or completed. Delivery is best-effort and does not block signing.
Webhook payloads do not include document contents; use the Bearer-token API artifact
endpoints to fetch the final PDF and ledger after envelope.completed.
Available events
| Event | When it fires |
|---|---|
webhook.test |
Manual test delivery for a configured webhook endpoint. |
envelope.sent |
An envelope is sent for signing for the first time. |
recipient.signed |
A recipient successfully completes signing. |
recipient.refused |
A recipient refuses to sign. The refusal reason is included in this event only. |
envelope.completed |
All required signing is complete and the final executed PDF / ledger have been generated. |
Webhook delivery headers
| Header | Meaning |
|---|---|
X-SecuSign-Event |
The event type, for example recipient.signed. |
X-SecuSign-Delivery |
Unique event delivery id. Use this for idempotency. |
X-SecuSign-Timestamp |
Unix timestamp used in the signature base string. |
X-SecuSign-Signature |
HMAC-SHA256 signature in the format v1=<hex>. |
User-Agent |
SecuSign-Webhooks/1.0 |
Payload shape
{
"id": "evt_...",
"type": "recipient.signed",
"created_at": "2026-05-17T05:38:51Z",
"data": {
"event": "recipient.signed",
"envelope": {
"id": "env_...",
"title": "Service Agreement",
"status": "completed",
"revision": 0,
"created_at": "...",
"updated_at": "...",
"first_sent_at": "...",
"completed_at": "...",
"voided_at": "",
"document": {
"filename": "agreement.pdf",
"size": 123456,
"pages": 4
},
"recipients": [
{
"name": "Jane Doe",
"email": "[email protected]",
"role": "signer",
"stage": 1,
"status": "signed",
"signed_at": "...",
"refused_at": "",
"stamp_mode": "none"
}
],
"cc_count": 0
},
"recipient": {
"name": "Jane Doe",
"email": "[email protected]",
"role": "signer",
"stage": 1,
"status": "signed",
"signed_at": "...",
"refused_at": "",
"stamp_mode": "none"
},
"meta": {
"recipient_index": 1
}
}
}
Verify the webhook signature
Verify the signature using the raw request body exactly as received. Do not parse and re-serialize the JSON before verification.
# Python / Flask-style example
import hmac
import hashlib
import time
def verify_secusign_webhook(raw_body: bytes, headers: dict, secret: str) -> bool:
timestamp = headers.get("X-SecuSign-Timestamp", "")
signature = headers.get("X-SecuSign-Signature", "")
if not timestamp or not signature.startswith("v1="):
return False
# Optional replay protection: reject payloads older than 5 minutes.
now = int(time.time())
try:
ts_int = int(timestamp)
except ValueError:
return False
if abs(now - ts_int) > 300:
return False
signed_payload = timestamp.encode("utf-8") + b"." + raw_body
expected = hmac.new(
secret.encode("utf-8"),
signed_payload,
hashlib.sha256
).hexdigest()
received = signature.split("=", 1)[1]
return hmac.compare_digest(expected, received)
Receiver behavior
- Return any
2xxstatus to mark delivery as successful. - Use
X-SecuSign-Deliveryor payloadidto ignore duplicate deliveries. - Process events asynchronously when possible, then respond quickly.
- Use HTTPS endpoints only. Local/private webhook targets are blocked in production.
- For
recipient.refused, handle the refusal reason as sensitive business context.
Configure webhook endpoints
Webhook configuration is available for Business accounts. The management endpoints are session-authenticated
account routes, while signing automation uses the Bearer-token /api/v1/* API.
# List configured webhooks for the logged-in account
GET /api/me/webhooks
# Create a webhook endpoint
POST /api/me/webhooks
Content-Type: application/json
{
"name": "Production webhook",
"url": "https://example.com/secusign/webhook",
"events": [
"envelope.sent",
"recipient.signed",
"recipient.refused",
"envelope.completed"
],
"enabled": true
}
# Test delivery
POST /api/me/webhooks/{webhook_id}/test
# Delete / disable endpoint
DELETE /api/me/webhooks/{webhook_id}
Rate limits
API calls are rate-limited per API key to protect service stability. Envelope sends still use the normal plan envelope limits.
| Plan | API request limit |
|---|---|
Personal | 60 requests per minute |
Business | 300 requests per minute |
Error model
Typical API errors return JSON with a detail message.
| Status | Meaning |
|---|---|
400 | Invalid payload, missing PDF, invalid email, invalid field position. |
401 | Missing or invalid Bearer API key. |
402 | Plan does not include API access. |
403 | API key lacks required scope. |
409 | Envelope is locked because it is no longer a draft. |
413 | Uploaded PDF is too large or has too many pages. |
429 | API rate limit exceeded. Retry after the period shown in the Retry-After response header. |