API reference

Sign documents with one API call

Nex-Sign is an embeddable e-signature API. Put placeholder tokens like $signature in your DOCX, POST it with a small JSON payload, and we convert it, email each signer a secure magic link, capture the signature, and return a sealed, tamper-evident PDF — then notify your webhook. Base URL https://nex-sign.com. Full machine-readable reference: openapi.json.

Quickstart

  1. Get a token — create an account and copy your API token (shown once, re-copyable from the dashboard).
  2. Prepare a DOCX — type a token wherever a signature or date goes, e.g. Signed: $signature and Date: $date_signed.
  3. Send it — one authenticated POST with the file + a JSON payload that maps each token to a signer.
  4. We handle the rest — conversion, delivery, signing, sealing, and webhooks.
Start free — 20 documents

Authentication

Every request is authenticated with a Bearer token; the business is inferred from the token. Every response uses a uniform envelope — success, data, error, meta.

Authorization: Bearer <YOUR_TOKEN>

Send a document

POST /v1/envelopes — a multipart request with one or more files and a JSON payload. An envelope can hold several documents and several signers. Returns 202 Accepted; signing runs asynchronously.

curl -X POST https://nex-sign.com/v1/envelopes \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -F "files=@agreement.docx" \
  -F 'payload={
    "subject": "Please sign your agreement",
    "externalId": "order-1024",
    "recipients": [
      { "email": "signer@acme.com", "fullName": "Alex Client" }
    ],
    "fields": [
      { "documentName": "agreement.docx",
        "recipientEmail": "signer@acme.com",
        "type": "signature",
        "anchorKeyword": "$signature" },
      { "documentName": "agreement.docx",
        "recipientEmail": "signer@acme.com",
        "type": "date_signed",
        "anchorKeyword": "$date_signed" }
    ]
  }'
  • recipients — the people who sign (email + fullName; optional routingOrder).
  • fields — where each field goes: a type, an anchorKeyword to find in the DOCX, the recipientEmail it belongs to, and which document — by documentIndex (0-based upload position, recommended) or documentName (rejected if two files share a name).
  • externalId (optional) — your own reference, echoed back on the envelope and every webhook.
  • sequential (optional) — see signing order.

Tip — one signature, many spots. If the same anchorKeyword appears several times in a document (e.g. a signature on page 1 and page 8), you declare it once. We place a signing block at every occurrence, and the signer adopts a signature a single time to fill them all.

Field types

Two field types cover the SES tier. Each field maps one anchor to one recipient.

signatureA signature the recipient adopts (typed or drawn). Placed as an image at the anchor.
date_signedThe date the recipient signs — filled automatically as text, no input needed.

Placing fields — text tokens or bookmarks

Choose per field how we locate it in the DOCX:

  • Text token (anchorType: "text", the default) — anchorKeyword is any literal string in the document, e.g. $signature or $date_signed. Just type it where the field should appear. Works in any font, size, weight, or table cell, and at every occurrence. Pick tokens unlikely to appear in real prose.
  • Word bookmark (anchorType: "bookmark") — anchorKeyword is a Word bookmark name. Bookmarks are structural, so they can never collide with visible text. In Word: click where the field goes → Insert → Bookmark → name it (e.g. nxsig1).

Your tokens don’t show in the output. Every declared anchor is replaced (a signature image, or the date text) and hidden in the sealed PDF and signed DOCX — you never see $signature in the result. Undeclared tokens are left untouched, so only place tokens you map to a field.

Signing order

Default is parallel — every recipient is emailed at once. Set "sequential": true for a chain: each signer is emailed only after the previous one signs (order from routingOrder, or recipient position). Any number of signers.

What you get back

The create call returns the envelope immediately. Poll GET /v1/envelopes/:id or — better — listen for webhooks to know when it’s done.

HTTP/1.1 202 Accepted
{
  "success": true,
  "data": {
    "id": "9fa2c8e1-2b7d-4f0a-8c11-5e6d3a1b0c42",
    "externalId": "order-1024",
    "subject": "Please sign your agreement",
    "status": "sent",
    "sequential": false,
    "sentAt": "2026-07-21T10:09:02Z",
    "completedAt": null,
    "createdAt": "2026-07-21T10:09:01Z"
  },
  "error": null,
  "meta": null
}

Envelope status

sentInvites emailed; awaiting signatures.
in_progressAt least one recipient has signed (multi-signer envelopes).
completedEvery recipient signed — sealed PDF & signed DOCX are ready.
declinedA recipient declined to sign.
voidedYou cancelled the envelope.
expiredThe signing window elapsed before completion.

Downloading documents

Get the docId from GET /v1/envelopes/:id, then GET /v1/envelopes/:id/documents/:docId/download?type=… returns JSON { "url", "expiresInSeconds": 900 } — a pre-signed link valid for 15 minutes (not a redirect). Four artifacts:

sourceThe original DOCX you uploaded.
renderedThe signer-facing preview PDF (markers shown, unsigned).
signed_docxThe signed DOCX — an editable working copy.
signed_pdfThe sealed, tamper-evident PDF with an audit certificate — the legal record.

Error codes

Failures use the same envelope with success: false and a stable error.code:

validation_failedA field in the payload is missing or malformed (422).
conflicte.g. a field references an unknown/ambiguous document (409).
payment_requiredNo document credits remaining (402).
unauthorizedMissing or invalid API token (401).
not_foundThe envelope or document does not exist (404).
rate_limitedToo many requests — retry after a short wait (429).

All endpoints

POST/v1/envelopesCreate & send an envelope. Multipart: files + a JSON payload. Returns 202.
GET/v1/envelopesList envelopes (cursor-paginated: ?limit, ?cursor, ?status).
GET/v1/envelopes/:idEnvelope detail — status, recipients, documents & artifact availability.
GET/v1/envelopes/:id/auditThe envelope audit trail (created, sent, viewed, signed, completed…).
GET/v1/envelopes/:id/documents/:docId/download?type=Short-lived signed download URL. type: source | rendered | signed_docx | signed_pdf.
POST/v1/envelopes/:id/remindRe-send the invite to recipients who have not signed yet.
POST/v1/envelopes/:id/voidVoid the envelope (cancel signing). Body: an optional reason.
POST/v1/envelopes/:id/recipients/:recipientId/signing-linkGenerate a fresh signing link (URL) for a recipient — send it via SMS or any channel.
GET/v1/business/email-templateGet this business signing-invite branding.
PATCH/v1/business/email-templateUpdate the invite branding (intro, button label/colour/align).

Webhooks

Set your endpoint and a secret with PATCH /v1/business/:id/webhook{ "webhookUrl", "webhookSecret" }. You choose the secret; we HMAC-SHA256 the raw body with it into X-NexSign-Signature on every delivery (the event name is also in X-NexSign-Event). Return 2xx to acknowledge; we retry for up to 24 hours otherwise.

Events

envelope.sentThe envelope was dispatched to signers.
recipient.viewedA signer opened the document for the first time.
recipient.signedA recipient signed (fires for each).
envelope.completedEvery recipient has signed; sealed PDF ready.
envelope.voidedThe envelope was canceled — its links no longer sign.
recipient.bouncedAn invite email could not be delivered.
// envelope.completed — every recipient has signed
{
  "event": "envelope.completed",
  "envelopeId": "9fa2c8e1-...",
  "externalId": "order-1024",
  "completedAt": "2026-07-21T10:31:02Z",
  "documents": [{ "id": "doc_...", "name": "agreement.docx" }]
}

// recipient.signed — one recipient signed (fires for each)
{
  "event": "recipient.signed",
  "envelopeId": "9fa2c8e1-...",
  "recipient": { "email": "signer@acme.com", "routingOrder": 1 },
  "envelopeStatus": "in_progress"
}

// recipient.bounced — an invite email could not be delivered
{
  "event": "recipient.bounced",
  "envelopeId": "9fa2c8e1-...",
  "recipient": { "email": "signer@acme.com", "reason": "bounce" }
}

Verify the signature against the raw request body before trusting an event:

// Node.js — verify the webhook signature
import crypto from 'node:crypto';

function isValid(rawBody, header, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)          // the EXACT raw request body
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(header),      // X-NexSign-Signature
  );
}

Ready to build?

Create an account, grab a token, and send your first 20 documents free.

Get your API token