Authentication
All API requests must include a valid API key in the Authorization header as a Bearer token:
Authorization: Bearer sk_live_your_key_hereAPI keys begin with sk_live_. They are generated in the Inktivo dashboard under Account settings → API. A key is shown only once at creation — store it securely in an environment variable. Treat API keys like passwords; do not commit them to source control.
Keys that are unused for an extended period or that you no longer need should be revoked from the dashboard.
Base URL
All endpoints are relative to:
https://inktivo.com/api/v1All requests and responses use application/json. CORS is enabled for all origins, so you can call the API directly from browser-based apps.
Errors
Inktivo uses standard HTTP status codes. When an error occurs, the response body contains a single error field with a human-readable message:
{
"error": "Only DRAFT envelopes can be sent"
}| Status | Meaning |
|---|---|
| 200 | OK — Request succeeded. |
| 201 | Created — Resource was created (e.g. webhook). |
| 204 | No Content — Success, no body (e.g. DELETE). |
| 400 | Bad Request — Invalid or missing parameters. |
| 401 | Unauthorized — Missing, invalid, or revoked API key. |
| 404 | Not Found — The requested resource does not exist or does not belong to your organization. |
| 422 | Unprocessable — The request was well-formed but the action cannot be performed (e.g. sending an already-sent envelope). |
Envelopes
https://inktivo.com/api/v1/envelopesReturns a paginated list of envelopes for your organization.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number. Defaults to 1. |
per_page | integer | Results per page. Defaults to 20, maximum 100. |
status | string | Filter by status: DRAFT, SENT, COMPLETED, VOIDED, or EXPIRED. |
Response
{
"data": [
{
"id": "clx1abc2def3",
"title": "NDA — Acme Corp",
"status": "SENT",
"signingOrder": "SEQUENTIAL",
"createdAt": "2025-06-01T10:00:00.000Z",
"sentAt": "2025-06-01T10:05:00.000Z",
"completedAt": null,
"voidedAt": null,
"expiresAt": null,
"documents": [
{ "id": "doc1", "name": "nda.pdf", "pageCount": 4 }
],
"recipients": [
{
"id": "rec1",
"name": "Jane Smith",
"email": "jane@example.com",
"role": "SIGNER",
"status": "SENT",
"completedAt": null
}
]
}
],
"pagination": {
"total": 42,
"page": 1,
"perPage": 20,
"totalPages": 3
}
}https://inktivo.com/api/v1/envelopes/:idReturns a single envelope with full detail including all recipient timestamps.
Response
{
"data": {
"id": "clx1abc2def3",
"title": "NDA — Acme Corp",
"status": "COMPLETED",
"signingOrder": "SEQUENTIAL",
"createdAt": "2025-06-01T10:00:00.000Z",
"sentAt": "2025-06-01T10:05:00.000Z",
"completedAt": "2025-06-01T14:22:00.000Z",
"voidedAt": null,
"expiresAt": null,
"documents": [
{ "id": "doc1", "name": "nda.pdf", "pageCount": 4, "order": 0 }
],
"recipients": [
{
"id": "rec1",
"name": "Jane Smith",
"email": "jane@example.com",
"role": "SIGNER",
"order": 1,
"status": "COMPLETED",
"sentAt": "2025-06-01T10:05:00.000Z",
"viewedAt": "2025-06-01T13:10:00.000Z",
"completedAt": "2025-06-01T14:22:00.000Z",
"declinedAt": null
}
]
}
}https://inktivo.com/api/v1/envelopes/:id/sendSends a DRAFT envelope for signing. Signing-invitation emails are delivered to the appropriate recipients immediately.
No request body is required.
Preconditions
- The envelope must be in
DRAFTstatus. - The envelope must have at least one recipient.
- At least one field must be placed on the documents.
Response (200)
{
"data": {
"id": "clx1abc2def3",
"status": "SENT"
}
}https://inktivo.com/api/v1/envelopes/:id/voidVoids an envelope. All recipient signing links become invalid immediately. Cannot be undone.
Request body
| Parameter | Type | Description |
|---|---|---|
reason | string | Optional reason for voiding (e.g. 'Terms changed'). Recorded in the audit trail. |
{
"reason": "Contract terms updated — new version incoming"
}Preconditions
- The envelope must not already be
COMPLETEDorVOIDED.
Response (200)
{
"data": {
"id": "clx1abc2def3",
"status": "VOIDED"
}
}Webhooks
Webhooks let Inktivo push event notifications to your server in real time. When a relevant event occurs (e.g. a recipient signs), Inktivo sends an HTTP POST request to your endpoint with a JSON payload.
https://inktivo.com/api/v1/webhooksReturns all webhook endpoints for your organization.
Response
{
"data": [
{
"id": "wh1abc",
"url": "https://myapp.example.com/webhooks/inktivo",
"events": ["envelope.completed", "recipient.signed"],
"enabled": true,
"createdAt": "2025-06-01T10:00:00.000Z"
}
]
}https://inktivo.com/api/v1/webhooksRegisters a new webhook endpoint.
Request body
| Parameter | Type | Description |
|---|---|---|
urlrequired | string | The HTTPS URL Inktivo will POST events to. Must start with https://. |
eventsrequired | string[] | One or more event types to subscribe to (see Event types below). |
{
"url": "https://myapp.example.com/webhooks/inktivo",
"events": ["envelope.completed", "envelope.voided", "recipient.signed"]
}Response (201)
secret field is returned only once. Store it securely — you will use it to verify webhook signatures.{
"data": {
"id": "wh1abc",
"url": "https://myapp.example.com/webhooks/inktivo",
"events": ["envelope.completed", "recipient.signed"],
"enabled": true,
"createdAt": "2025-06-01T10:00:00.000Z",
"secret": "a3f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1"
}
}https://inktivo.com/api/v1/webhooks/:idPermanently deletes a webhook endpoint. No more events will be delivered to it.
Returns 204 No Content on success.
Webhook event types
Pass one or more of the following strings in the events array:
| Event | Triggered when |
|---|---|
envelope.sent | An envelope is sent and signing invitations are dispatched. |
envelope.completed | All required signers have completed the envelope. |
envelope.voided | An envelope is voided by a team member or via the API. |
recipient.signed | A single recipient completes their required fields. |
recipient.declined | A recipient declines to sign and provides a reason. |
Payload format
Every webhook POST has Content-Type: application/json and a body of the form:
// envelope.completed example
{
"event": "envelope.completed",
"data": {
"envelopeId": "clx1abc2def3",
"title": "NDA — Acme Corp"
},
"timestamp": "2025-06-01T14:22:00.000Z"
}The data object for each event:
| Event | data fields |
|---|---|
| envelope.sent | envelopeId, title |
| envelope.completed | envelopeId, title |
| envelope.voided | envelopeId, title, reason (may be empty) |
| recipient.signed | envelopeId, recipientId, recipientName, recipientEmail |
| recipient.declined | envelopeId, recipientId, recipientName, recipientEmail, reason |
Verifying webhook signatures
Inktivo signs every webhook request with HMAC-SHA256 using the secret returned when you created the endpoint. Two headers are included:
X-Inktivo-Signature—sha256=<hex digest>X-Inktivo-Event— the event type string (e.g.envelope.completed)
To verify, compute HMAC-SHA256(secret, rawBody) and compare it to the hex digest in the X-Inktivo-Signature header (after stripping the sha256= prefix). Always use a constant-time comparison to prevent timing attacks.
Node.js verification example
import crypto from "crypto";
export function verifyInktivoWebhook(
secret, // the secret you saved when creating the webhook
rawBody, // the raw request body string (before JSON.parse)
signature, // the X-Inktivo-Signature header value
) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const received = signature.replace(/^sha256=/, "");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex"),
);
}
// Express handler example
app.post("/webhooks/inktivo", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-inktivo-signature"];
const event = req.headers["x-inktivo-event"];
if (!verifyInktivoWebhook(process.env.INKTIVO_WEBHOOK_SECRET, req.body.toString(), sig)) {
return res.status(401).send("Invalid signature");
}
const payload = JSON.parse(req.body.toString());
if (event === "envelope.completed") {
console.log("Envelope completed:", payload.data.envelopeId);
// trigger your downstream workflow here
}
res.sendStatus(200);
});Python (Flask) verification example
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
INKTIVO_SECRET = os.environ["INKTIVO_WEBHOOK_SECRET"]
@app.route("/webhooks/inktivo", methods=["POST"])
def inktivo_webhook():
signature = request.headers.get("X-Inktivo-Signature", "")
event = request.headers.get("X-Inktivo-Event", "")
raw_body = request.get_data() # must be raw bytes
expected = hmac.new(
INKTIVO_SECRET.encode(),
raw_body,
hashlib.sha256,
).hexdigest()
received = signature.removeprefix("sha256=")
if not hmac.compare_digest(expected, received):
abort(401)
payload = request.get_json()
if event == "envelope.completed":
print(f"Completed: {payload['data']['envelopeId']}")
return "", 200Code examples
cURL — list envelopes
curl -s -H "Authorization: Bearer sk_live_your_key_here" \
"https://inktivo.com/api/v1/envelopes?status=SENT&per_page=5" | jq .cURL — send an envelope
curl -s -X POST \
-H "Authorization: Bearer sk_live_your_key_here" \
"https://inktivo.com/api/v1/envelopes/clx1abc2def3/send" | jq .Node.js — fetch and process completed envelopes
const API_KEY = process.env.INKTIVO_API_KEY;
const BASE_URL = "https://inktivo.com/api/v1";
async function fetchCompleted(page = 1) {
const res = await fetch(
`${BASE_URL}/envelopes?status=COMPLETED&page=${page}&per_page=50`,
{ headers: { Authorization: `Bearer ${API_KEY}` } },
);
if (!res.ok) {
const { error } = await res.json();
throw new Error(`Inktivo API error: ${error}`);
}
const { data, pagination } = await res.json();
for (const envelope of data) {
console.log(`${envelope.title} — completed at ${envelope.completedAt}`);
}
if (pagination.page < pagination.totalPages) {
await fetchCompleted(page + 1);
}
}
fetchCompleted().catch(console.error);Python — void an envelope
import os, requests
API_KEY = os.environ["INKTIVO_API_KEY"]
BASE_URL = "https://inktivo.com/api/v1"
def void_envelope(envelope_id: str, reason: str = ""):
response = requests.post(
f"{BASE_URL}/envelopes/{envelope_id}/void",
json={"reason": reason},
headers={"Authorization": f"Bearer {API_KEY}"},
)
response.raise_for_status()
return response.json()["data"]
result = void_envelope("clx1abc2def3", reason="Contract terms revised")
print(result) # {'id': 'clx1abc2def3', 'status': 'VOIDED'}Node.js — create a webhook
const res = await fetch(`https://inktivo.com/api/v1/webhooks`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.INKTIVO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://myapp.example.com/webhooks/inktivo",
events: ["envelope.completed", "envelope.voided", "recipient.signed"],
}),
});
const { data } = await res.json();
// IMPORTANT: save data.secret — it will not be shown again
console.log("Webhook secret:", data.secret);
console.log("Webhook ID:", data.id);