Developers · API · Webhooks

One POST. Out for signature.

A REST API to send PDFs for signature and follow every signer, signed webhooks for each step, idempotency keys and stable error codes. API keys and webhooks come with the Enterprise plan.

See what you can build: signatures from your own software
curl https://api.wesign.now/v1/signing-requests \
  -H "Authorization: Bearer $WSK_KEY" \
  -H "Idempotency-Key: order-123/sign" \
  -F "file=@contract.pdf" \
  -F 'signers=[{"email":"jane@acme.com","role":"client"}]' \
  -F "placement=auto_append"

# → 201 Created
# {
#   "documentId": "8a1e4f9a-…",
#   "status": "pending",
#   "signers": [{
#     "role": "client",
#     "signingUrl": "https://acme.wesign.now/en/sign/…",
#     "emailed": true, …
#   }], …
# }
What you get

An API, webhooks and docs your tools can read.

REST API

Send a PDF, follow each signer, remind or withdraw, and download the sealed PDF and its audit trail. Bearer keys, JSON responses and an OpenAPI 3.1 spec with an id for every operation.

API overview

Signed webhooks

signing_request.sent, .viewed, .signed, document.completed and more, each signed with HMAC-SHA256. A failed delivery is retried for about 45 hours; the delivery log lets you send it again.

Webhook events

Docs for AI assistants

llms.txt lists every docs page, llms-full.txt holds them all as plain text, and openapi.json the exact request and response shapes. Hand them to your coding assistant.

For AI assistants

Concrete examples.

Four calls most integrations need. Field names as in the OpenAPI spec.

1. Send with anchor placement

Put a marker such as [[ls:signature:buyer]] in your PDF. We place the buyer's signature field there and mask the marker. No marker in the PDF? We append a signature page.

POST /v1/signing-requests
Content-Type: multipart/form-data

file=@offer.pdf        # the PDF contains [[ls:signature:buyer]]
signers=[{"email":"buyer@acme.com","role":"buyer","name":"Ann Buyer"}]
placement=anchors

2. Several signers, in order

Three signers, one after the other. Signer 2 is invited only after signer 1 has signed.

POST /v1/signing-requests
Content-Type: multipart/form-data

file=@contract.pdf
signing_mode=sequential
placement=auto_append
signers=[
  {"email":"founder@acme.com","role":"founder"},
  {"email":"investor@vc.example","role":"investor"},
  {"email":"witness@law.example","role":"witness"}
]

3. Webhook receiver

Check the timestamp and the HMAC over the raw body, then act on the event.

// Node + Express. The full verifier (Node and PHP) is in the docs.
app.post('/wesign/callback', express.raw({ type: 'application/json' }), (req, res) => {
  // X-WeSign-Signature: t=<unix seconds>,v1=<hex>  (two v1 for 24 h after a rotation)
  const parts = (req.get('x-wesign-signature') ?? '').split(',')
  const t = parts[0]?.startsWith('t=') ? parts[0].slice(2) : ''
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(401).end()
  const expected = crypto.createHmac('sha256', SECRET)
    .update(`${t}.${req.body}`).digest()
  const valid = parts.filter((p) => p.startsWith('v1=')).some((p) => {
    const got = Buffer.from(p.slice(3), 'hex')
    return got.length === expected.length && crypto.timingSafeEqual(got, expected)
  })
  if (!valid) return res.status(401).end()

  const evt = JSON.parse(req.body.toString('utf8'))
  if (evt.event === 'document.completed') {
    // fetch evt.signed_pdf_url with your API key, compare evt.sha256, keep a copy
  }
  res.sendStatus(200)
})

4. Idempotent retry

The same key with the same body within 24 hours replays the first response, so a retry after a network error never sends twice.

POST /v1/signing-requests
Idempotency-Key: order-123/sign

# First call:                      201 Created
# Same key + same body (24 h):     201, the same body, Idempotent-Replayed: true
# Same key while the first runs:   409 idempotency_in_progress + Retry-After
# Same key + a different body:     422 idempotency_key_reuse

Built to run in production.

Bearer keys, two scopes

Each key belongs to one workspace. A full key calls the whole API; an embedded key only manages embedded signing sessions. Limit a key to your IP addresses; revoking one takes effect at once.

60 requests per minute per key

One fixed window per key across all /v1 endpoints. Successful responses carry RateLimit-* headers; a 429 carries Retry-After.

Stable error codes

Every error is JSON with a readable error, a stable code and, where it helps, meta. Match on the code; the message may change.

Sealed, verifiable PDFs

The signed PDF carries a PAdES seal any compliant reader can check. The audit trail lists every event with its time, the signer's email and masked IP address, and every SMS-code check.

API access comes with Enterprise.

API keys and webhooks are part of the Enterprise plan. Tell us what you want to connect. The docs are public, so you can read them first.