Skip to content
Menu

API and webhooks

Create, send, track and download signature requests from your own software, and get notified when they change.

Updated October 15, 2026 View as Markdown

The eSign API lets your own software send PDFs for signature and download the sealed result. eSign calls your software back (a webhook) whenever a request changes.

Address and key

All requests go to your eSign address followed by /api/v1, for example https://esign.sapiensandmachines.com/api/v1, and carry your workspace's API key:

HTTP
Authorization: Bearer YOUR_API_KEY
Accept: application/json

API keys belong to the workspace, not to a person, and don't expire. S+M creates them for you; each is shown once. Ask us to revoke a key you no longer use. Up to 120 requests a minute.

Send a document

POST /envelopes with the PDF and the people who sign. Add send=1 to send it straight away. In this example the key is in the ESIGN_API_KEY environment variable.

Shell
curl -X POST https://esign.sapiensandmachines.com/api/v1/envelopes \
  -H "Authorization: Bearer $ESIGN_API_KEY" -H "Accept: application/json" \
  -F title="Service agreement" \
  -F document=@agreement.pdf \
  -F "recipients[0][key]=client" \
  -F "recipients[0][name]=Ana García" \
  -F "recipients[0][email]=ana@example.com" \
  -F "recipients[0][role]=signer" \
  -F locale=es \
  -F send=1
Field
title Required, up to 255 characters.
document or document_base64 The PDF (up to 25 MB, not password-protected).
recipients[] 1 to 20 people: key (your own name for them), name, email, role (signer or cc), routing_order (1–20; the same number signs at the same time), verification (none or email_code for a 6-digit code by email).
message Shown to signers, up to 5,000 characters.
locale en or es; otherwise your workspace's signer language.
expires_in_days 1 to 365; 30 if not given.
external_ref Your own reference, to find the request later.
callback_url Where to send this request's webhooks, instead of your workspace's address.
send true to send now; otherwise it stays a draft.
fields[] Optional: place fields by position instead of markers.

The PDF's markers decide where each signer signs: @2 in a marker means the second signer in recipients.

Other calls

Call Does
GET /envelopes Lists requests; filter by status or external_ref; per_page up to 100.
GET /envelopes/{id} One request, with each recipient's status.
POST /envelopes/{id}/send Sends a draft.
POST /envelopes/{id}/void Cancels it (reason); not once it's completed.
GET /envelopes/{id}/document The sealed PDF once completed (409 before that); its SHA-256 is in the X-Document-Sha256 header.
GET /envelopes/{id}/audit The history, and whether its chain of records is intact (chain_intact).

A request's status is draft, sent, completed, declined, voided or expired. Each recipient's status shows whether they have viewed or signed.

Webhooks

eSign sends a POST with JSON to the request's callback_url, or else to your workspace's webhook address, for:

Event When
recipient.viewed A signer opened the document.
recipient.signed A signer signed.
envelope.completed Everyone signed; the sealed PDF is ready.
envelope.declined A signer declined.
envelope.expired The request expired.
envelope.voided It was cancelled.
JSON
{
  "event": "envelope.completed",
  "occurred_at": "2026-10-15T15:04:05Z",
  "recipient": null,
  "envelope": {
    "id": "…",
    "status": "completed",
    "title": "Service agreement",
    "external_ref": "QUOTE-1042",
    "sealed_sha256": "…",
    "completed_at": "2026-10-15T15:04:05Z",
    "document_url": "…"
  }
}

For recipient events, recipient has their id, name, email, status, viewed_at, signed_at and, for a decline, decline_reason.

Answer with any 2xx status. Otherwise eSign tries again after 10 seconds, 1 minute, 5 minutes, 15 minutes and 1 hour.

Check that a webhook is from eSign

Each webhook carries X-Esign-Event and X-Esign-Signature: sha256=…, an HMAC-SHA256 of the raw body with your webhook secret. Compute it yourself and compare before trusting the body:

PHP
$body = file_get_contents('php://input');
$expected = 'sha256='.hash_hmac('sha256', $body, $webhookSecret);
if (! hash_equals($expected, $_SERVER['HTTP_X_ESIGN_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}

Errors

Errors come back as JSON with an HTTP status: 401 for a missing or wrong key, 403 when the workspace is suspended, 404 for an unknown request, 409 when asking for the document before it's completed, 422 with the reasons when something in the request is wrong (for example a marker for a signer who wasn't given, or sending a request that isn't a draft), and 429 when there are too many requests.

Was this page helpful?

Add a comment (optional)

We keep your answer and comment, nothing about who sent them.