REST API · WEBHOOKS · v1

Bouwen met BuiltSign

Integreer juridisch geldige e-handtekeningen direct in je eigen applicatie. Upload pdf's, verstuur ondertekentransacties, plaats handtekeningvelden en ontvang webhook-events, allemaal via een eenvoudige REST API. De API hoort bij Enterprise; goedgekeurde partners testen gratis met een sandbox-sleutel.

Base URL
url
https://api.builtsign.com/v1

Snel starten

Een document laten ondertekenen in 4 API-aanroepen.

1. Maak een API-sleutel aan

Ga naar Instellingen → API-sleutels in je BuiltSign-dashboard en maak een sleutel aan met schrijfrechten. Sla hem veilig op, hij wordt maar één keer getoond.

Settings → API Keys

2. Upload een PDF

Upload een PDF (tot 50 MB) als multipart-formulierdata. Je ontvangt een document_id voor de volgende stap. Grotere bestanden, tot 1 GB, gaan in delen via POST /v1/uploads.

bash
curl -X POST https://api.builtsign.com/v1/documents/upload \
  -H "X-API-Key: bs_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@contract.pdf"

3. Maak een ondertekentransactie

Maak een ondertekentransactie aan met het document_id en een lijst van ondertekenaars. Stel auto_send: false in om eerst velden te plaatsen.

bash
curl -X POST https://api.builtsign.com/v1/signing-requests \
  -H "X-API-Key: bs_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "document_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "signers": [
      { "name": "Jan de Vries", "email": "jan@example.com" }
    ],
    "message": "Graag de offerte ondertekenen.",
    "expires_in_days": 7,
    "auto_send": false
  }'

4. Voeg velden toe en verstuur

Plaats handtekeningvelden met procentuele coördinaten en verstuur vervolgens om de uitnodigingsmail te sturen.

curl -X POST https://api.builtsign.com/v1/signing-requests/{request_id}/fields \
  -H "X-API-Key: bs_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": [
      {
        "signer_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "field_type": "signature",
        "page_number": 1,
        "x_percent": 60.0,
        "y_percent": 82.0,
        "width_percent": 30.0,
        "height_percent": 8.0
      }
    ]
  }'

Partner-quickstart: een groot document

Laat een tekeningenset of opleverdossier van honderden MB's ondertekenen en zet het resultaat in je eigen systeem. Zes stappen, allemaal in de sandbox.

  1. 1. Sandbox-sleutel en webhook

    Maak een sandbox-sleutel aan (Instellingen → API-sleutels, vink Sandbox aan). Een webhook die je met die sleutel aanmaakt, krijgt alleen sandbox-events. Bewaar het geheim uit het antwoord; het wordt één keer getoond.

  2. 2. Upload de PDF in delen

    uploadLarge stuurt het bestand in delen rechtstreeks naar de opslag, tot 1 GB, meerdere delen tegelijk. Een mislukt deel wordt opnieuw geprobeerd met een nieuwe URL.

  3. 3. Maak de transactie en plaats velden

    Maak de transactie aan met auto_send: false en een Idempotency-Key die bij je eigen record hoort, en plaats daarna de velden in procenten per pagina.

  4. 4. Verstuur

    Verstuur met een Idempotency-Key, zodat een nieuwe poging na een netwerkfout nooit dubbel verstuurt. In de sandbox gaat er geen mail of sms uit: open de ingebedde ondertekenlink zelf en teken.

  5. 5. Ontvang en controleer de webhook

    Controleer X-BuiltSign-Webhook-Signature op de ruwe body met webhooks.verify, antwoord direct met 2xx en sla een event_id over die je al verwerkt hebt.

  6. 6. Haal de getekende PDF en het auditcertificaat op

    Bij document.signed haal je de getekende PDF op via de link van 15 minuten en het auditcertificaat als PDF. Een 409 document_not_ready betekent: wacht Retry-After seconden en probeer het opnieuw.

De hele flow in Node

typescript
import { createServer } from "node:http";
import { writeFile } from "node:fs/promises";
import BuiltSign, { BuiltSignError } from "@builtsign/node";

const builtsign = new BuiltSign({ apiKey: process.env.BUILTSIGN_API_KEY! }); // sandbox key
if (!(await builtsign.me()).sandbox) throw new Error("Use a sandbox key");

// 1. A webhook made with a sandbox key only gets sandbox events. The secret is shown once.
const webhook = await builtsign.webhooks.create({
  name: "Docstream",
  url: "https://your-host.example.com/builtsign",
  events: ["document.signed", "signer.declined", "signing_request.expired"],
});

// 2. Up to 1 GB, in parts straight to storage
const document = await builtsign.documents.uploadLarge("drawing-set.pdf", { concurrency: 4 });

// 3. A draft, tied to your own record, then the fields (percent of the page)
const request = await builtsign.signingRequests.create(
  {
    document_id: document.document_id,
    signers: [{ name: "Jan de Vries", email: "jan@example.com", order: 1 }],
    auto_send: false,
  },
  { idempotencyKey: `docstream-${recordId}` },
);
await builtsign.signingRequests.addFields(request.id, [{
  signer_id: request.signers[0].id, field_type: "signature",
  page_number: 1, x_percent: 60, y_percent: 82, width_percent: 30, height_percent: 8,
}]);

// 4. Send. Sandbox: no mail goes out, so sign through the embedded link yourself
await builtsign.signingRequests.send(request.id, { idempotencyKey: `send-${request.id}` });
const { signers } = await builtsign.signingRequests.embeddedUrl(request.id, { allowed_origin: "http://localhost:3000" });
console.log("Sign here:", signers[0].embedded_url);

// 5. Verify every delivery on the raw body, answer 2xx at once, deduplicate on event_id
const handled = new Set<string>();
createServer(async (req, res) => {
  const chunks: Buffer[] = [];
  for await (const chunk of req) chunks.push(chunk);
  let event;
  try {
    event = builtsign.webhooks.verify(Buffer.concat(chunks), req.headers["x-builtsign-webhook-signature"], webhook.secret);
  } catch {
    return res.writeHead(401).end();
  }
  res.writeHead(200).end();
  if (handled.has(event.event_id)) return;
  handled.add(event.event_id);
  if (event.event === "document.signed") await saveResults(event.data.signing_request_id as string);
}).listen(4567);

// 6. Signed PDF and audit certificate; 409 document_not_ready means: wait Retry-After
async function saveResults(id: string) {
  const { download_url } = await whenReady(() => builtsign.signingRequests.download(id));
  await writeFile(`${id}-signed.pdf`, new Uint8Array(await (await fetch(download_url)).arrayBuffer()));
  await writeFile(`${id}-audit-certificate.pdf`, await whenReady(() => builtsign.signingRequests.auditCertificate(id)));
}

async function whenReady<T>(call: () => Promise<T>): Promise<T> {
  for (;;) {
    try {
      return await call();
    } catch (err) {
      if (!(err instanceof BuiltSignError) || err.code !== "document_not_ready") throw err;
      await new Promise((r) => setTimeout(r, (err.retryAfter ?? 5) * 1000));
    }
  }
}

Test alleen met een sandbox-sleutel. Het voorbeeld controleert eerst GET /v1/me en stopt bij een live sleutel.

Authenticatie

Alle v1-endpoints vereisen een API-sleutel in de X-API-Key request header.

bash
curl https://api.builtsign.com/v1/signing-requests \
  -H "X-API-Key: bs_xxxxxxxxxxxxxxxxxxxxxxxx"

Rechten

ScopePermissions
readBronnen ophalen en weergeven
writeBronnen aanmaken, bijwerken en versturen
adminGereserveerd voor toekomstig gebruik

Sandbox-sleutels

Vink Sandbox aan bij het aanmaken van een sleutel. Verzoeken via die sleutel worden als sandbox gemarkeerd: uitnodigingen en herinneringen gaan niet uit, er is geen ID-check of sms en niets telt mee voor je documentverbruik. Getekende documenten krijgen een watermerk en hebben geen juridische waarde, en sandboxdata wordt na 30 dagen verwijderd. Geslaagde antwoorden bevatten de header X-BuiltSign-Mode: sandbox, en webhook-events bij uploaden, aanmaken, versturen en ondertekenen het veld is_sandbox. Nog geen Enterprise-plan? Vraag gratis sandbox-toegang aan; je sleutels hebben dan standaard een limiet van 30 verzoeken per minuut en 500 per dag.

OpenAPI-specificatie

Importeer de specificatie in een codegenerator, of blader er interactief doorheen. Voor Postman is er een kant-en-klare collectie met de variabelen baseUrl en apiKey, en een nieuwe Idempotency-Key bij elke aanmaak- en verstuuraanroep. openapi.json · Interactieve referentie · Postman-collectie

Limieten

Voor elke sleutel gelden twee limieten: verzoeken per minuut en verzoeken per dag. Elk antwoord laat zien waar je staat.

Elke sleutel heeft een limiet per minuut: standaard 60. Je kunt per sleutel een eigen limiet instellen, maar die komt nooit boven de limiet van je organisatie. De daglimiet (standaard 2000 aanroepen) begint om middernacht UTC opnieuw. Sandbox-sleutels van partners zonder Enterprise: 30 per minuut en 500 per dag.

Headers in elk antwoord

HeaderBetekenis
X-RateLimit-LimitToegestane verzoeken deze minuut
X-RateLimit-RemainingResterende verzoeken deze minuut
X-RateLimit-ResetUnix-tijd waarop het minuutvenster opnieuw begint
X-RateLimit-Daily-LimitToegestane verzoeken vandaag (UTC)
X-RateLimit-Daily-RemainingResterende verzoeken vandaag
X-RateLimit-Daily-ResetUnix-tijd van de volgende middernacht UTC
Retry-AfterBij 429 en 503: seconden wachten voor je het opnieuw probeert

429 en 503

Boven de limiet per minuut geeft de API 429 met rate_limit_exceeded en Retry-After: wacht zoveel seconden en stuur dezelfde aanroep opnieuw. Boven de daglimiet geeft de API 429 met daily_limit_exceeded, en Retry-After loopt tot middernacht UTC. Is de limiter zelf even onbereikbaar, dan krijg je 503 met service_unavailable en Retry-After. Er is dan niets uitgevoerd, dus opnieuw proberen is veilig, ook zonder Idempotency-Key.

http
HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1793577617
X-RateLimit-Daily-Limit: 2000
X-RateLimit-Daily-Remaining: 1412
X-RateLimit-Daily-Reset: 1793664000

{"error": {"code": "rate_limit_exceeded", "message": "Rate limit exceeded. Please slow down.", "request_id": "req_4e092e9aa07f4103a14f"}}

De Node SDK wacht op Retry-After en probeert 429 en 503 zelf opnieuw (maxRetries, standaard 3).

Idempotency

Probeer een aanmaak- of verstuuraanroep veilig opnieuw: met dezelfde Idempotency-Key voert de API hem maar één keer uit.

Stuur een Idempotency-Key-header mee (een unieke tekst van maximaal 255 tekens) bij aanroepen die iets aanmaken of versturen: uploads, ondertekentransacties, versturen, annuleren, herinneren, sjablonen, versturen vanuit een sjabloon en bulkverzending. Een nieuwe poging met dezelfde sleutel en dezelfde body geeft het eerste antwoord terug met de header Idempotency-Replay: true, in plaats van het opnieuw uit te voeren. Sleutels worden 24 uur per API-sleutel bewaard.

bash
curl -X POST https://api.builtsign.com/v1/signing-requests \
  -H "X-API-Key: bs_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: order-1234" \
  -H "Content-Type: application/json" \
  -d '{"document_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "signers": [{"name": "Jan de Vries", "email": "jan@example.com"}]}'

# Same call again: the first response, with
# Idempotency-Replay: true
SituatieAntwoord
Zelfde sleutel, zelfde body, eerste aanroep klaarHet eerste antwoord, met Idempotency-Replay: true
Zelfde sleutel, andere body of ander endpoint422 idempotency_key_reused. Gebruik een nieuwe sleutel voor elk nieuw verzoek.
Zelfde sleutel terwijl de eerste aanroep nog looptDe API wacht een paar seconden op het antwoord. Loopt hij dan nog: 409 idempotency_key_in_use met Retry-After
De sleutelopslag is even onbereikbaar503 service_unavailable met Retry-After. Er is niets uitgevoerd.

Alleen geslaagde antwoorden worden bewaard. Na een fout, bijvoorbeeld een 402 of 422, voert een nieuwe poging met dezelfde sleutel opnieuw uit, zodat je de oorzaak kunt oplossen en het nog eens kunt proberen.

De Node SDK zet zelf een sleutel op elke aanmaak- en verstuuraanroep en gebruikt die opnieuw bij zijn eigen nieuwe pogingen. Geef idempotencyKey mee om hem aan je eigen record te koppelen, bijvoorbeeld order-1234.

Paginering

Lijsten nemen limit (tot 100) met offset of met een cursor.

Pagineren met offset (limit en offset) is prima om een pagina te tonen. Het antwoord bevat items en total.

Gebruik voor synchroniseren de cursor op GET /v1/signing-requests, GET /v1/documents en GET /v1/events: geef de next_cursor van een pagina mee als starting_after, tot has_more false is. Items die intussen bijkomen of verdwijnen, laten je nooit een item overslaan of dubbel krijgen.

curl "https://api.builtsign.com/v1/documents?limit=100" \
  -H "X-API-Key: bs_xxxxxxxxxxxxxxxxxxxxxxxx"
# { "items": [...], "total": 214, "has_more": true, "next_cursor": "3fa85f64-..." }

curl "https://api.builtsign.com/v1/documents?limit=100&starting_after=3fa85f64-..." \
  -H "X-API-Key: bs_xxxxxxxxxxxxxxxxxxxxxxxx"

offset samen met starting_after geeft 422 validation_error, net als een starting_after die geen item uit deze lijst is. Items met dezelfde tijd staan op id, dus de volgorde blijft tussen aanroepen gelijk. Op /v1/events blijft de oudere after_id werken.

Documenten

Upload pdf's en bekijk eerder geüploade documenten.

Ondertekentransacties

Maak ondertekentransacties aan, beheer ze en volg de status.

Handtekeningvelden

Plaats velden op een document die ondertekenaars moeten invullen voordat zij kunnen afronden.

Positionering

Coördinaten zijn procentueel (0–100) ten opzichte van de pagina-afmetingen. Het oorsprongspunt (0, 0) is de linkerbovenhoek van de pagina.

(0, 0)(100, 0)(0, 100)(100, 100)
signature
initials

Veldtypen

field_typeDescription
signatureVolledige handtekening
initialsParaaf / initialen
dateDatum (automatisch ingevuld)
textVrije tekstinvoer
checkboxAanvinkvak
selectDropdown (geef options[] mee)

Webhooks

Ontvang real-time meldingen wanneer er iets gebeurt in BuiltSign, in het dashboard of via de API.

Instellen

Maak een webhook aan via Instellingen → Webhooks, of met je API-sleutel via POST /v1/webhooks. Kies op welke events je wilt abonneren. BuiltSign stuurt een HTTP POST naar jouw endpoint voor elk event.

Events

EventTrigger
signing_request.createdOndertekentransactie aangemaakt
signing_request.sentTransactie verzonden naar ondertekenaars
signing_request.completedAlle ondertekenaars hebben getekend
signing_request.cancelledOndertekentransactie geannuleerd
signing_request.expiredOndertekentransactie verlopen
signer.viewedOndertekenaar heeft de pagina geopend
signer.signedOndertekenaar heeft ondertekend
signer.declinedOndertekenaar heeft geweigerd
signer.withdrawnOndertekenaar heeft handtekening ingetrokken
signer.reviewedID-check van een ondertekenaar wacht op handmatige beoordeling
identity.approvedID-check van een ondertekenaar goedgekeurd
identity.rejectedID-check van een ondertekenaar afgekeurd
document.signedOndertekende PDF gegenereerd
signing_request.remindedHerinneringsmails verstuurd naar openstaande ondertekenaars
document.uploadedDocument geüpload via API
member.invitedTeamlid uitgenodigd voor de organisatie

Payload-formaat

Elke webhook-POST bevat deze velden:

json
{
  "event_id": "evt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "event": "signing_request.completed",
  "timestamp": "2026-05-21T11:00:00Z",
  "webhook_id": "wh_xxxxxxxxxxxxxxxxxxxx",
  "data": {
    "signing_request_id": "sr_xxxxxxxxxxxxxxxxxxxx",
    "completed_at": "2026-05-21T11:00:00Z",
    "all_signers": [
      { "email": "alice@example.com", "name": "Alice Smith", "signed_at": "2026-05-21T10:30:00Z" },
      { "email": "bob@example.com", "name": "Bob Jones", "signed_at": "2026-05-21T11:00:00Z" }
    ],
    "signed_pdf_s3_key": "orgs/org_xxx/signed/sr_xxx.pdf"
  }
}

Request Headers

HeaderValue
Content-Typeapplication/json
X-BuiltSign-Webhook-Signaturet=<unix seconds>,v1=<HMAC-SHA256 hex>, a second v1 right after a secret rotation
X-Webhook-Delivery-IDdelivery UUID, the same on automatic retries
X-Webhook-Eventevent type (e.g. signer.signed)
X-Webhook-Timestampunix seconds, the same as t
X-Webhook-IDwebhook UUID
X-Webhook-Retryattempt number on retries, resend on a redelivery
X-Webhook-Testtrue on test events only
X-Webhook-Signaturedeprecated: HMAC-SHA256 hex over the body only

Handtekeningverificatie

Controleer elke webhook met de header X-BuiltSign-Webhook-Signature. Bereken HMAC-SHA256 met je webhookgeheim over de timestamp t, een punt en de ruwe body, vergelijk die in constante tijd met elke v1 en weiger een t die meer dan 5 minuten van je eigen klok afwijkt. Vertrouw de payload nooit zonder deze controle.

import crypto from "crypto";

const TOLERANCE_SECONDS = 300;

function verifyBuiltSign(rawBody, header, secret) {
  let t;
  const signatures = [];
  for (const part of String(header ?? "").split(",")) {
    const [key, value] = part.split("=", 2);
    if (key === "t") t = Number(value);
    if (key === "v1") signatures.push(value);
  }
  if (!Number.isInteger(t) || signatures.length === 0) return false;
  // Refuse old timestamps, so a captured request cannot be replayed.
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(t + ".")
    .update(rawBody) // raw Buffer, not parsed JSON
    .digest();
  // Constant-time compare; after a secret rotation there are two v1 values.
  return signatures.some((sig) => {
    const given = Buffer.from(sig, "hex");
    return given.length === expected.length && crypto.timingSafeEqual(given, expected);
  });
}

// Express middleware example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const valid = verifyBuiltSign(
    req.body,
    req.headers["x-builtsign-webhook-signature"],
    process.env.BUILTSIGN_WEBHOOK_SECRET,
  );
  if (!valid) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(req.body);
  // Retries repeat event.event_id: skip an event you already handled.
  // TODO: handle event (e.g. event.event === "document.signed")
  res.sendStatus(200);
});

Je ontvanger gebouwd vóór 22 oktober 2026? De header X-Webhook-Signature, een HMAC over alleen de body, wordt nog meegestuurd maar is uitgefaseerd. Hij heeft geen tijdstempel, dus een onderschept verzoek kan opnieuw worden verstuurd. Stap over op X-BuiltSign-Webhook-Signature; de einddatum kondigen we minstens 90 dagen vooraf aan in de changelog.

Herhalingen

Een levering slaagt bij elk 2xx-antwoord binnen 10 seconden. Een mislukte levering probeert BuiltSign nog 9 keer, met exponentiële backoff en spreiding: na ongeveer 1 minuut, 5 minuten, 30 minuten, 2 uur en 6 uur, daarna elke 12 uur, samen ruim 2,5 dag. Pauzeer je de webhook, dan stoppen de herhalingen. Een webhook gaat automatisch uit als 10 events op rij mislukken over minstens 3 dagen.

Dubbele leveringen

Elke payload heeft een event_id dat bij elke herhaling en elke nieuwe levering gelijk blijft, en elk verzoek heeft een X-Webhook-Delivery-ID. Bewaar de event_id van de events die je verwerkt hebt en sla een event over dat je al kent.

Het geheim vervangen

Na het opnieuw genereren tekent het vorige geheim nog 24 uur mee: de header bevat dan een tweede v1. Een ontvanger met het oude geheim keurt leveringen dus nog goed, zodat je het nieuwe geheim kunt uitrollen zonder events te missen. Via de API kies je de overlap met overlap_hours, van 0 tot 168. De overlap geldt alleen voor X-BuiltSign-Webhook-Signature: de oude X-Webhook-Signature gebruikt direct het nieuwe geheim.

Testen en opnieuw leveren

  • POST /v1/webhooks/{webhook_id}/test Stuurt een ondertekend test-event met "test": true en de header X-Webhook-Test.
  • POST /v1/webhooks/deliveries/{delivery_id}/redeliver Stuurt een opgeslagen levering opnieuw, met dezelfde body en event_id.

Node.js SDK

Zero-dependency TypeScript client voor Node.js ≥ 18. Dekt elk API-endpoint met volledige type-veiligheid en ingebouwde retry-logica.

Installatie

bash
npm install @builtsign/node

TypeScript · Node.js ≥ 18 · Zero dependencies

Snelstart

typescript
import fs from "node:fs/promises";
import BuiltSign from "@builtsign/node";

const builtsign = new BuiltSign({ apiKey: process.env.BUILTSIGN_API_KEY! });

// 1. Upload a PDF
const { document_id } = await builtsign.documents.upload(
  await fs.readFile("contract.pdf"),
  "contract.pdf",
);

// 2. Create a signing request
const request = await builtsign.signingRequests.create({
  document_id,
  signers: [
    { name: "Jan de Vries", email: "jan@example.com", order: 1 },
    { name: "Lisa Smit",    email: "lisa@example.com", order: 2 },
  ],
  signing_order: "sequential",
  expires_in_days: 7,
  auto_send: false,
});

// 3. Place signature fields
await builtsign.signingRequests.addFields(request.id, [{
  signer_id: request.signers[0].id,
  field_type: "signature",
  page_number: 1,
  x_percent: 60,
  y_percent: 82,
  width_percent: 30,
  height_percent: 8,
}]);

// 4. Send
await builtsign.signingRequests.send(request.id);

API resources

ResourceMethods
builtsign.documentsupload(), uploadLarge(), list(), iterate(), get(), delete()
builtsign.signingRequestscreate(), list(), iterate(), get(), events(), send(), cancel(), addFields(), download(), auditCertificate(), remind(), reactivate(), embeddedUrl()
builtsign.templateslist(), create(), get(), update(), delete(), send()
builtsign.contactslist(), create(), delete()
builtsign.webhookslist(), get(), create(), update(), delete(), regenerateSecret(), getDeliveries(), test(), redeliver(), verify()
builtsign.eventslist(), iterate()
builtsign.bulksend(), getJob()
builtsign.usageget()
builtsignme()

Wat er per versie veranderd is, staat in CHANGELOG.md van het pakket.

MCP Server

Laat AI-assistenten documenten uploaden en ondertekentransacties versturen via het Model Context Protocol.

Wat is MCP?

Het Model Context Protocol (MCP) is een open standaard van Anthropic waarmee AI-assistenten zoals Claude externe tools kunnen aanroepen. De BuiltSign MCP-server biedt 15 tools zodat een AI je ondertekenwerkstroom van begin tot eind kan beheren.

Installatie

bash
# Claude Code
claude mcp add builtsign \
  -e BUILTSIGN_API_KEY=bs_xxxxxxxxxxxxxxxxxxxxxxxx \
  -e BUILTSIGN_API_URL=https://api.builtsign.com \
  -- npx -y @builtsign/mcp

Claude Desktop configuratie

json
{
  "mcpServers": {
    "builtsign": {
      "command": "npx",
      "args": ["-y", "@builtsign/mcp"],
      "env": {
        "BUILTSIGN_API_KEY": "bs_xxxxxxxxxxxxxxxxxxxxxxxx",
        "BUILTSIGN_API_URL": "https://api.builtsign.com"
      }
    }
  }
}

Beschikbare tools

upload_documentlees een lokale PDF en upload hem
list_documents
create_signing_requestconcept aanmaken
create_and_send_signing_requestaanmaken en direct versturen
send_signing_requestbestaand concept versturen
get_signing_requeststatus opvragen
list_signing_requestsmet statusfilter en cursor-paginatie
cancel_signing_request
send_reminder
list_templates
send_from_templaterollen koppelen aan ondertekenaars
list_eventsvolledige audit trail met cursor-paginatie

Bring Your Own AI (BYOAI)

Koppel je eigen AI-pipeline aan BuiltSign. Na elke voltooide ondertekening ontvang je een webhook met een downloadlink (15 minuten geldig) naar het getekende PDF. Verwerk het volledig op je eigen infrastructuur.

Enterprise only

Hoe het werkt

01

Document getekend

Alle ondertekenaars voltooien het ondertekteningsverzoek. BuiltSign genereert de definitieve PDF en slaat die beveiligd op in S3.

02

Webhook verstuurd

BuiltSign stuurt direct een JSON-payload naar jouw endpoint, inclusief een presigned URL om het getekende PDF te downloaden (15 minuten geldig).

03

Jouw AI verwerkt

Jouw pipeline downloadt de PDF en doet wat nodig is: data extraheren, archiveren, workflows starten. BuiltSign ziet de output nooit.

Wat je kunt bouwen

Contractgegevens (namen, bedragen, datums) extraheren en naar CRM of ERP sturen

Vervolgworkflows starten: onboarding, provisioning, compliance-controles

Slack-meldingen sturen met een AI-samenvatting van het getekende document

Getekende contracten in een RAG-pipeline laden voor interne documentzoekopdrachten

Instellen

BYOAI wordt per organisatie ingesteld. Je geeft een HTTPS-endpoint op en genereert een signing secret.

1

Ga naar Instellingen → Ontwikkelaars → Bring Your Own AI

2

Voer je HTTPS-endpoint URL in en genereer een signing secret

3

Deploy je webhook-handler. Gebruik het signing secret om elk verzoek te verifiëren (zie code hieronder)

AI-integratie-instellingen openen

Webhook-payload

BuiltSign stuurt een POST-verzoek met deze JSON-body. De signed_pdf_url is een presigned S3-URL die 15 minuten geldig is.

json
{
  "event": "document.signed",
  "signing_request_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "document_name": "Koopcontract 2026.pdf",
  "completed_at": "2026-06-16T10:30:00Z",
  "signed_pdf_url": "https://s3.amazonaws.com/builtsign/signed/...?X-Amz-Expires=900",
  "expires_in": 900,
  "signers": [
    {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "signed_at": "2026-06-16T10:28:00Z"
    },
    {
      "name": "Lisa Smit",
      "email": "lisa@example.com",
      "signed_at": "2026-06-16T10:30:00Z"
    }
  ]
}

signed_pdf_url verloopt na 15 minuten. Download de PDF direct in je handler.

Verifieer X-BuiltSign-Signature bij elk verzoek. Weiger alles wat niet overeenkomt.

Handtekening verifiëren en PDF downloaden

Verifieer altijd de HMAC-SHA256-handtekening voordat je de payload verwerkt. Gebruik de ruwe request-body (geen geparseerde JSON) voor de HMAC-berekening.

import crypto from "crypto";
import express from "express";

app.post("/ai-webhook", express.raw({ type: "application/json" }), async (req, res) => {
  // 1. Verify the signature
  const signature = req.headers["x-builtsign-signature"];
  const secret    = process.env.BUILTSIGN_AI_SECRET;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(req.body)          // raw Buffer: do NOT parse first
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return res.status(401).send("Invalid signature");
  }

  const payload = JSON.parse(req.body);

  // 2. Download the signed PDF (URL valid 15 minutes)
  const pdfResponse = await fetch(payload.signed_pdf_url);
  const pdfBuffer   = await pdfResponse.arrayBuffer();

  // 3. Send to your AI pipeline
  await yourAiPipeline.process({
    documentName: payload.document_name,
    pdf:          Buffer.from(pdfBuffer),
    signers:      payload.signers,
    completedAt:  payload.completed_at,
  });

  res.sendStatus(200);
});

Request-headers

HeaderValue
Content-Typeapplication/json
X-BuiltSign-SignatureHMAC-SHA256 hex digest of raw body
X-BuiltSign-Eventdocument.signed

Klaar om je AI te koppelen?

Stel je endpoint in via Instellingen. Klaar in minder dan 2 minuten.

Nu instellen

Embedded Signing

Laat ondertekenaars documenten direct in jouw applicatie ondertekenen. Geen doorverwijzing naar builtsign.com.

0. Registreer je origin

Een admin zet de origin van je app, zoals https://app.example.com, op de embed-allowlist onder Instellingen > Integraties (maximaal 20). Alleen die origins kunnen de tekenpagina in een iframe laden. Voor ontwikkelen werkt http://localhost.

1. Genereer een embedded URL (server-side)

Roep embeddedUrl() (POST /v1/signing-requests/:id/embedded-url) aan vanuit je backend met de allowed origin van je frontend. Je krijgt per ondertekenaar een URL die 60 minuten in een iframe te laden is. Behandel hem als geheim: wie hem heeft, kan tekenen.

typescript
const { signers } = await builtsign.signingRequests.embeddedUrl(
  request.id,
  {
    allowed_origin: "https://yourapp.com",
    // Optional: where the iframe goes after signing or declining
    return_url: "https://yourapp.com/contracts/123/done",
  },
);

// One URL per signer, valid for 60 minutes
const signerUrl = signers[0].embedded_url;

return_url is optioneel. Na tekenen of weigeren gaat het iframe daarheen, met builtsign_event=signed of builtsign_event=declined en builtsign_request_id erbij. De origin moet op je allowlist staan; BuiltSign controleert dat bij het maken van de URL en bij elke keer laden.

2. Mount het signing iframe (client-side)

Installeer @builtsign/embed en geef de URL door aan BuiltSignEmbed. Die maakt het iframe, accepteert alleen berichten van dat iframe en van de BuiltSign-origin, en roept je callbacks aan.

bash
npm install @builtsign/embed
typescript
import { BuiltSignEmbed } from "@builtsign/embed";

const embed = new BuiltSignEmbed({
  container: "#signing-container",  // <div id="signing-container">
  embeddedUrl: signerUrl,

  onReady: (e) => console.log("Ready", e.requestId),
  onSigned: (e) => {
    console.log("Signed", e.requestId, e.documentName);
    embed.close();
  },
  onDeclined: (e) => {
    console.log("Declined", e.reason);
    embed.close();
  },
  onError: (e) => {
    console.error(e.code, e.message);
    if (!e.recoverable) embed.close();
  },
});

embed.open();

Events

EventWanneer
builtsign:readyDe pagina is geladen en kan getekend worden. Velden: requestId, signerId, signerStatus, requestStatus.
builtsign:signedDe ondertekenaar heeft getekend. Velden: requestId, signerId, documentName.
builtsign:declinedDe ondertekenaar heeft geweigerd. Velden: requestId, signerId, reason.
builtsign:errorDe pagina kan niet getekend worden, of een actie mislukte. Velden: code, message, recoverable, requestId, signerId.

Foutcodes: invalid_link, load_failed, embed_session_invalid, signing_request_expired, signing_request_cancelled, signing_request_completed, signer_declined, sign_failed en decline_failed. Bij recoverable: true kan de ondertekenaar het in het iframe opnieuw proberen. De SDK voegt ready_timeout toe als er binnen 30 seconden geen ready komt, meestal omdat de URL verlopen is of je pagina niet de allowed origin is. De pagina stuurt elk bericht alleen naar je allowed origin, nooit naar *.

typescript
// Without the SDK: check origin and source yourself
window.addEventListener("message", (event) => {
  if (event.origin !== "https://builtsign.com") return;
  if (event.source !== iframe.contentWindow) return;
  if (event.data?.type === "builtsign:signed") {
    // event.data.requestId, event.data.signerId
  }
});

Bevestig op je server

De events komen uit de browser. Bevestig een handtekening met de webhook signer.signed of document.signed, of met GET /v1/signing-requests/:id, voordat je erop handelt.

Safari, Firefox en geblokkeerde opslag

De tekenpagina heeft geen cookies of lokale opslag nodig om te tekenen, dus hij werkt ook als de browser die in iframes van derden blokkeert. Zet geen sandbox-attribuut op het iframe. De optionele ID-check gebruikt de camera; de SDK staat die toe op het iframe.

Versiebeleid en changelog

v1 blijft stabiel. Elke wijziging staat in de changelog.

  • Een wijziging die een correcte koppeling kan breken, kondigen we minstens 90 dagen vooraf aan in de changelog.
  • Niet brekend, en zonder aankondiging: nieuwe endpoints, nieuwe optionele parameters, nieuwe velden in antwoorden en webhook-payloads, nieuwe webhook-events, nieuwe foutcodes en nieuwe headers. Negeer wat je niet kent, en reageer op error.code en niet op de melding.
  • Een endpoint dat uitgefaseerd wordt, antwoordt met een Deprecation-header en een Link naar de changelog, en met Sunset (RFC 8594) zodra de einddatum vaststaat.
  • Beveiligingsfixes kunnen zonder aankondiging live gaan. Ook die staan in de changelog, met hun datum.

Wat een uitgefaseerd endpoint teruggeeft

http
HTTP/1.1 200 OK
Deprecation: @1793577600
Sunset: Mon, 01 Feb 2027 00:00:00 GMT
Link: <https://builtsign.com/developers/changelog>; rel="deprecation"; type="text/html",
      <https://builtsign.com/developers/changelog>; rel="sunset"; type="text/html"

Op dit moment is er geen endpoint uitgefaseerd.

Open de changelog

Enterprise

Gebouwd voor enterprise-teams

Heb je white-label branding, een eigen e-maildomein of contractafspraken nodig? BuiltSign is een SaaS-product, geen installatie vereist. Neem contact met ons op voor enterprise-onboarding, maatwerkcontracten en geavanceerde integraties.

  • White-label API & onderteken-UI
  • REST API en webhooks
  • Eigen e-maildomein
  • Contractuele aantallen en opslag
  • SLA + prioriteitssupport
  • Aangepaste limieten & volumeprijzen
Contact opnemen →

Foutcodes

Elke fout geeft een JSON-body met een error-object met code, message en request_id. De code is machineleesbaar; noem de request_id als je contact opneemt met support.

json
{
  "error": {
    "code": "quota_exceeded",
    "message": "Not enough SMS left this period (100/100 used, 2 needed).",
    "request_id": "req_4e092e9aa07f4103a14f",
    "details": { "resource": "sms", "used": 100, "limit": 100, "needed": 2 }
  }
}
StatusOmschrijving
400Ongeldig verzoek, ongeldige invoer of schending van een beperking
401Niet geautoriseerd, ontbrekende of ongeldige API-sleutel
402Betaling vereist, het plan dekt deze verzending niet (proef voorbij, transactielimiet, sms-bundel) of te weinig tegoed
403Verboden, onvoldoende rechten of planbeperking
404Niet gevonden, bron bestaat niet of behoort tot een andere organisatie
409Conflict, actie niet toegestaan in de huidige status
410Weg, de getekende PDF is na de bewaartermijn verwijderd
413Te groot, het bestand is groter dan de limiet (tot 1 GB via POST /v1/uploads)
422Onverwerkbaar, validatiefout (controleer velddetails)
429Te veel verzoeken, limiet overschreden
502Upstream-fout, S3 of e-mailprovider tijdelijk niet beschikbaar
503Even niet beschikbaar, er is niets uitgevoerd: probeer opnieuw na Retry-After seconden

Foutcodes

Reageer op error.code, niet op de melding. Er kunnen codes bijkomen; behandel een onbekende code volgens zijn HTTP-status. Veel fouten hebben ook details, bijvoorbeeld used en limit.

error.codeOmschrijving
validation_error422 of 400: een veld ontbreekt of is ongeldig. De veldfouten staan in detail.
auth_api_key_invalid401: de sleutel is onbekend, ingetrokken of verlopen.
auth_scope_missing403: de sleutel mist het recht (read of write).
feature_not_in_plan403: de functie zit niet in je plan. details zegt welk plan hem heeft.
sandbox_not_supported403: niet beschikbaar met een sandbox-sleutel, zoals opgeslagen contacten.
embed_origin_not_allowed403: de origin voor ingebed tekenen staat niet op je lijst. Voeg hem toe onder Instellingen, Integraties.
quota_exceeded402 of 403: een plangrens is bereikt. details.resource zegt welke (signing_requests, sms, storage, document_volume).
insufficient_credits402: te weinig tegoed voor ID-checks.
payload_too_large413: het bestand is groter dan de limiet. details.limit bevat die.
signing_request_not_found404: geen transactie met dit id in je organisatie en modus.
document_not_found404: geen document met dit id in je organisatie en modus.
signing_request_wrong_status409: kan niet in de huidige status, bijvoorbeeld het certificaat van een open transactie.
document_not_ready409: de getekende PDF of het certificaat wordt nog gemaakt. Probeer opnieuw na Retry-After.
document_blocked422: de malwarescan heeft dit document geweigerd. Upload een schoon bestand.
document_has_open_request409: het document zit in een open transactie. Annuleer die eerst.
document_has_evidence409: het document bevat bewijs dat 7 jaar bewaard blijft.
document_file_removed410: het bestand is na de bewaartermijn verwijderd.
template_name_exists409: je hebt al een actief sjabloon met deze naam.
idempotency_key_reused422: deze Idempotency-Key is voor een ander verzoek gebruikt.
idempotency_key_in_use409: de eerste aanroep met deze sleutel loopt nog. Probeer opnieuw na Retry-After.
rate_limit_exceeded429: limiet per minuut bereikt. Probeer opnieuw na Retry-After.
daily_limit_exceeded429: daglimiet voor API-aanroepen bereikt. Die gaat om middernacht UTC opnieuw in; probeer niet opnieuw voor Retry-After.
service_unavailable503: even niet beschikbaar, er is niets uitgevoerd. Probeer opnieuw na Retry-After.