API y webhooks
Crea, envía, sigue y descarga solicitudes de firma desde tu propio software, y recibe avisos cuando cambian.
La API de eSign permite que tu propio software envíe PDF a firmar y descargue el resultado sellado. eSign avisa a tu software (un webhook) cada vez que una solicitud cambia.
Dirección y clave
Todas las llamadas van a tu dirección de eSign seguida de /api/v1, por ejemplo
https://esign.sapiensandmachines.com/api/v1, y llevan la clave de API de tu espacio:
Authorization: Bearer YOUR_API_KEY
Accept: application/jsonLas claves de API pertenecen al espacio, no a una persona, y no vencen. S+M las crea por ti; cada una se muestra una sola vez. Pídenos revocar una clave que ya no uses. Hasta 120 llamadas por minuto.
Envía un documento
POST /envelopes con el PDF y las personas que firman. Agrega send=1 para enviarlo de inmediato. En este ejemplo
la clave está en la variable de entorno ESIGN_API_KEY.
curl -X POST https://esign.sapiensandmachines.com/api/v1/envelopes \
-H "Authorization: Bearer $ESIGN_API_KEY" -H "Accept: application/json" \
-F title="Contrato de servicio" \
-F document=@contrato.pdf \
-F "recipients[0][key]=cliente" \
-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| Campo | |
|---|---|
title |
Obligatorio, hasta 255 caracteres. |
document o document_base64 |
El PDF (hasta 25 MB, sin contraseña). |
recipients[] |
De 1 a 20 personas: key (tu propio nombre para ella), name, email, role (signer o cc), routing_order (1–20; el mismo número firma a la vez), verification (none o email_code para un código de 6 dígitos por correo). |
message |
Se muestra a los firmantes, hasta 5.000 caracteres. |
locale |
en o es; si no, el idioma de los firmantes de tu espacio. |
expires_in_days |
De 1 a 365; 30 si no se indica. |
external_ref |
Tu propia referencia, para encontrar la solicitud después. |
callback_url |
Adónde enviar los webhooks de esta solicitud, en lugar de la dirección de tu espacio. |
send |
true para enviar ahora; si no, queda como borrador. |
fields[] |
Opcional: coloca los campos por posición en lugar de con marcadores. |
Los marcadores del PDF deciden dónde firma cada firmante: @2 en un marcador es el segundo firmante de recipients.
Otras llamadas
| Llamada | Hace |
|---|---|
GET /envelopes |
Lista las solicitudes; filtra por status o external_ref; per_page hasta 100. |
GET /envelopes/{id} |
Una solicitud, con el estado de cada destinatario. |
POST /envelopes/{id}/send |
Envía un borrador. |
POST /envelopes/{id}/void |
La cancela (reason); no una vez completada. |
GET /envelopes/{id}/document |
El PDF sellado una vez completada (409 antes); su SHA-256 va en el encabezado X-Document-Sha256. |
GET /envelopes/{id}/audit |
El historial, y si su cadena de registros está intacta (chain_intact). |
El status de una solicitud es draft, sent, completed, declined, voided o expired. El estado de cada
destinatario indica si ya vio o firmó.
Webhooks
eSign envía un POST con JSON al callback_url de la solicitud o, si no hay, a la dirección de webhooks de tu
espacio, para:
| Evento | Cuándo |
|---|---|
recipient.viewed |
Un firmante abrió el documento. |
recipient.signed |
Un firmante firmó. |
envelope.completed |
Todos firmaron; el PDF sellado está listo. |
envelope.declined |
Un firmante lo rechazó. |
envelope.expired |
La solicitud venció. |
envelope.voided |
Se canceló. |
{
"event": "envelope.completed",
"occurred_at": "2026-10-15T15:04:05Z",
"recipient": null,
"envelope": {
"id": "…",
"status": "completed",
"title": "Contrato de servicio",
"external_ref": "COT-1042",
"sealed_sha256": "…",
"completed_at": "2026-10-15T15:04:05Z",
"document_url": "…"
}
}En los eventos de destinatario, recipient incluye su id, name, email, status, viewed_at, signed_at y,
si rechazó, decline_reason.
Responde con cualquier estado 2xx. Si no, eSign lo reintenta a los 10 segundos, 1 minuto, 5 minutos, 15 minutos y
1 hora.
Comprueba que un webhook viene de eSign
Cada webhook lleva X-Esign-Event y X-Esign-Signature: sha256=…, un HMAC-SHA256 del cuerpo sin modificar con tu
secreto de webhooks. Calcúlalo y compáralo antes de confiar en el contenido:
$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;
}Errores
Los errores vuelven como JSON con un estado HTTP: 401 si falta la clave o es incorrecta, 403 si el espacio está
suspendido, 404 si la solicitud no existe, 409 si pides el documento antes de que esté completado, 422 con los motivos
cuando algo de la solicitud está mal (por ejemplo, un marcador para un firmante que no se indicó, o enviar una
solicitud que no es un borrador) y 429 si hay demasiadas llamadas.