Quickstart

From zero to a signed PDF in under five minutes — terminal-only, no SDK.

This guide goes deeper than the home page summary. By the end you'll have downloaded a real PAdES-sealed PDF, seen a webhook fire against your endpoint, and you'll know which knobs to turn for production.

Every URL below uses the canonical host https://api.wesign.now/v1. https://api.letssign.now/v1 is a permanent alias of the same API — see Base URL for what is and isn't the contract.

Prerequisites

  • A workspace on Enterprise. API keys and the template endpoints both ship with it; Free, Pro and Branded can't mint keys. Only a workspace owner or admin can.
  • Legacy Teams workspaces can still mint keys and use everything on this page. What they can't do is send from templates: instantiate, generate and confirm answer 402 enterprise-required there (and so does minting an embedded signing session). Listing and reading templates (GET /v1/templates, GET /v1/templates/{id}) works on Teams too.
  • A test PDF on disk. Any PDF works; if it doesn't have an anchor marker we just append a signature page.
  • An email address you can check (or two — sender + signer can be the same address while you smoke-test).

Sending from a template instead of a PDF? The Template quickstart walks through that flow.

1 · Mint an API key

In your workspace, open Developers → API keys and hit Create API key. Copy the wsk_live_… plaintext to a terminal env var:

export WSK_KEY="wsk_live_…"   # paste the whole key here

The plaintext shows once. The dashboard keeps a wsk_live_a1b… prefix so you can identify the key later. A key minted before the September 2026 rename starts with lsk_live_ and works exactly the same — no need to re-mint.

2 · Send a real request

curl -X POST https://api.wesign.now/v1/signing-requests \
  -H "Authorization: Bearer $WSK_KEY" \
  -H "Idempotency-Key: smoke-$(date +%s)" \
  -F "file=@/path/to/contract.pdf" \
  -F 'signers=[{"email":"you@example.com","role":"signer"}]' \
  -F 'placement=auto_append' | jq .

Successful response (HTTP 201):

{
  "documentId":      "8a1e4f9a-b3c1-4d22-9e0a-3a92ee7c1f88",
  "metadata":        null,
  "status":          "pending",
  "placement":       "auto_append",
  "presentedSha256": "4f9a…d21c",
  "anchors":         { "found": 0, "fields": 2 },
  "signers": [
    {
      "role":                     "signer",
      "email":                    "you@example.com",
      "status":                   "pending",
      "signingUrl":               "https://yourco.letssign.now/en/sign/abc…",
      "signingRequestId":         "11111111-…",
      "emailed":                  true,
      "require_sms_verification": false,
      "phone_masked":             null,
      "sms_verified_at":          null,
      "sms_gate":                 "before_sign",
      "signature_level":          "SES",
      "reference":                null,
      "company":                  null,
      "job_title":                null
    }
  ],
  "observers": [],
  "warnings":  []
}

"placement": "auto_append" with anchors.found: 0 means the PDF had no anchor markers, so a signature page was appended.

emailed: true says we sent the invitation. That email carries the same signing link on the email host — https://yourco.wesign.now/en/sign/abc… — while the API hands you the yourco.letssign.now form; both open the same page, the token decides. Open signingUrl in another browser tab to play the signer side. To deliver the link yourself instead, send -F 'send_emails=false': everything else is created as usual and emailed comes back false.

3 · Watch a webhook fire (optional)

Skip if you'd rather poll. To see the events live, the easiest path is a free Webhook.site URL:

# webhook.site/<your-token> issued at https://webhook.site
curl -X POST https://api.wesign.now/v1/signing-requests \
  -H "Authorization: Bearer $WSK_KEY" \
  -F "file=@contract.pdf" \
  -F 'signers=[{"email":"you@example.com","role":"signer"}]' \
  -F 'callback_url=https://webhook.site/YOUR-TOKEN'

The response now includes a callback.secret — store it. Each event we POST to your URL is signed. The same three values arrive under two header names; the X-LetsSign-* family is the pre-rename spelling and is kept forever, so read whichever you like:

X-WeSign-Signature:   t=1757404800,v1=9c3b…a2f1
X-WeSign-Event-Id:    evt_…
X-WeSign-Event:       signing_request.signed
X-LetsSign-Signature: t=1757404800,v1=9c3b…a2f1
X-LetsSign-Event-Id:  evt_…
X-LetsSign-Event:     signing_request.signed

Verify the signature before trusting the body.

4 · Sign

Open signingUrl on your phone (real test) or in another browser tab (lazy test). Sign. Watch the inbox for the completion email and your webhook receiver for signing_request.signed → document.completed.

5 · Pull the signed PDF programmatically

DOC_ID=8a1e4f9a-b3c1-4d22-9e0a-3a92ee7c1f88   # the documentId from step 2
curl -LOJ -H "Authorization: Bearer $WSK_KEY" \
  https://api.wesign.now/v1/documents/$DOC_ID/signed

Returns the fully PAdES-sealed PDF — every signer's signature appended, RFC 3161 timestamp embedded, ready for your DMS. This is the same URL document.completed hands you as signed_pdf_url: it needs your key, so pin that fetch to api.wesign.now and send the bearer — see signed_pdf_url.

Testing without a sandbox

There are no test keys yet: every key is live, every call creates real documents and sends real email and SMS, and SMS codes count against the monthly allowance. Three switches keep a test contained:

  • send_emails: false on POST /v1/signing-requests or instantiate — the document and the signing links are created and returned, no invitation is emailed, not even to the next signer of a sequential send when their turn comes. Open the links yourself. Automatic reminders (7 and 14 days after the send, by default) and the completion emails still go out, so use addresses you own, and withdraw what you leave unsigned.
  • validate_only: true on instantiate and generate — the full check (on instantiate the SMS allowance included) and nothing is created. See Templates → dry run.
  • review: true on instantiate (or generate) — the document is built and staged, nothing is sent. Look at it at the review_url, then throw it away with POST /v1/documents/{id}/discard. See Review before sending.

A sandbox with simulated email and SMS is planned — see Versioning → No sandbox yet.

What's next

  • Template quickstart — send the same contract many times from a locked template.
  • Placement modes — pick anchors, auto-append, or explicit coordinates.
  • Second factor by SMS — require a one-time code on the signer's phone before they can sign.
  • Webhooks — production-grade event delivery, retries, signature verification.
  • Idempotency — safely retry network blips.
  • Errors — full code table.