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 softwarecurl 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, …
# }], …
# }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 overviewSigned 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 eventsDocs 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 assistantsConcrete 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=anchors2. 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_reuseWhat you can build today.
Each pattern uses the REST API and webhooks. The cards link to the docs that describe it.
With send_emails: false we email no invitation, and each signer's link comes back in the response for your own email, SMS or app. Reminders and the completion email still come from us.
send_emailsTheir HTTP step calls the API with your key, and their webhook step receives our events once you register it with POST /v1/hooks. Nothing to install.
Hooks for automation toolsYour handler takes the CRM's stage change, sends the contract and updates the record when our webhook reports the signature. You run the handler; there is no CRM app.
CRM recipeDocassemble, Pandoc or LaTeX writes anchor markers into the PDF, one POST sends it, and you fetch the signed PDF when document.completed arrives.
Generator recipeHave the agent write markers such as [[ls:signature:client]] into the draft, and the fields land there. Every signature is still made by a person who opens their link.
Agent recipeMint a short-lived session on your server and show the signing view in an iframe on your own site. Your signers need no account.
Embedded signingNot available: an SDK, a sandbox or test keys, and apps or plug-ins for other tools.
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.
