# Add SignSealer e-signatures to my application

Integrate SignSealer (https://signsealer.com) so my application can send documents for
signature, or show the signing page inside it, and act when they are signed.
Read the docs as you go: every page is Markdown at https://signsealer.com/docs/<page>.md, and
https://signsealer.com/llms-full.txt is all of them in one file.

## My application

<my_stack>
Stack: [framework and language, e.g. Next.js 15 + TypeScript, Django, Rails, Laravel, Express]
What signs: [waivers | rental agreements | contracts | onboarding forms | other]
How signers get the page: [we email/text them (SignSealer sends) | inside our app (embedded) | both]
Who the signer is in my app: [the table and id that identify a person, e.g. bookings.id or users.id]
Database: [Postgres | MySQL | SQLite | Mongo | other]
Templates: [already made in the SignSealer dashboard | create them from code]
</my_stack>

## Rules

- Never invent an SDK method, a route, a field, an event name or a result. If
  the docs do not show it, it does not exist: say so and stop.
- The API key is server-side only. Read it from SIGNSEALER_KEY. Never send it
  to a browser, a mobile app or a log line.
- Start with a test key (mk_test_...). Nothing is emailed, texted or billed
  from the sandbox, and webhooks are delivered the same way. Move to a live
  key (mk_live_...) only when everything below passes.
- Never store a signing link. It is a bearer credential. Mint a fresh one when
  the page is shown (embedded), or send a reminder, which carries a fresh one.
- Never trust a signer id or document id that came from the browser. Look it
  up from the person who is signed in to my app.
- Verify every webhook against the raw request body before parsing it, and
  deduplicate on the SignSealer-Event-Id header: delivery is at least once.
- Send an Idempotency-Key on every POST that creates something (the SDKs do it
  for you), so a retry never makes a second document.

## Step 1: the client

Node: npm install @signsealer/sdk (no dependencies). Python: pip install
signsealer. PHP: composer require signsealer/sdk. Any other language: the REST
API at https://api.signsealer.com with "Authorization: Bearer $SIGNSEALER_KEY",
described by https://signsealer.com/openapi.json. Confirm the key with GET /v1/account, which
answers "mode": "test" for a test key.

## Step 2: the template

If the business made its templates in the dashboard, list them with
GET /v1/templates and use the code. Otherwise create one with POST /v1/templates
(every {{placeholder}} declared in "variables") and publish it with
POST /v1/templates/{id}/publish. A published version never changes; revise
with POST /v1/templates/{id}/revise. See https://signsealer.com/docs/templates.md.

## Step 3: a document, and who signs it

POST /v1/documents with "template_code" and "values" for every placeholder.
Then POST /v1/documents/{id}/send with "signers": each has a full_name and an
email, a phone, or both. Store the document id and each signer id against the
person in my database. See https://signsealer.com/docs/quickstart.md.

## Step 4 (embedded only): the signing page inside my app

Send with "delivery": "embedded" on the signer, so nothing is emailed. When
the person opens my screen, call POST /v1/documents/{id}/signers/{signer}/embed
for a fresh url and put it in an iframe (allow="camera; geolocation"). The
page may be framed only on a site the business has proven: "frame_origins" in
the answer lists them, and empty means nowhere yet. Listen for the page's
postMessage events (source "signsealer"). See https://signsealer.com/docs/embedding.md.

## Step 5: the webhook

On start-up, PUT /v1/webhooks with my endpoint's url and the events I need
(signing.completed at least; signing.declined, signing.expired and
signing.viewed are often wanted). It never makes a second endpoint; keep the
"secret" it returns when "created" is true. In the handler, verify with the
SDK's verifyWebhookRequest (Node) or its Python and PHP equivalents, or by
hand: SignSealer-Signature is v1=<hex>, HMAC-SHA256 of "timestamp.body" with
the secret, and SignSealer-Timestamp must be within five minutes. Answer 2xx
within ten seconds and do the work after. On signing.completed, fetch
GET /v1/documents/{id} and the signed PDF from GET /v1/documents/{id}/pdf.
See https://signsealer.com/docs/webhooks.md.

## Step 6: errors

Every error is { "error": { "code", "message", "hint" } }. Show the message;
retry only 429 (after Retry-After) and 5xx, with the same Idempotency-Key.
402 quota_exceeded means the business is out of signings: tell a person, do
not retry. See https://signsealer.com/docs/errors.md.

## Before it goes live

1. With the test key: create, send, and sign it -- from the link yourself, or
   with POST /v1/documents/{id}/signers/{signer}/simulate and "outcome":
   "signed" -- and see signing.completed arrive, verified, exactly once.
2. Send the same webhook twice by hand and check the second does nothing.
3. Change one byte of a delivery and check it is refused.
4. Decline a document ("outcome": "declined") and let one expire
   ("outcome": "expired"), and check my app shows both.
5. Embedded: open the page twice and check the first link stops working.
6. Show me the code and the test results before switching to the live key.
