Integrasiedokumentasie

WebhooksIntydse gebeurtenisse

Geteken stuur 'n HTTP POST na jou eindpunt telkens as die status van 'n dokument verander — onmiddellik, met 'n ondertekende HMAC-handtekening.

Vinnige begin

  1. Gaan na Account → Webhooks in die dashboard en voeg jou eindpunt-URL by.
  2. Kies die gebeure waarop jy wil inteken (of teken in op almal).
  3. Kopieer jou webhooksgeheim en stoor dit veilig — dit word eenmalig gewys.
  4. Bevestig elke inkomende versoek deur die X-Geteken-Signature kop te verifieer.

Gebeure

Elke webhook-aflewering dra n top-vlak event-veld. Hier is al die gebeure waarop jy kan inteken:

Interne gebeurtenisnaamWire event-waardeBeskrywing
envelope.sentdocument_state_changedDokument is aan alle ontvangers gestuur.
envelope.vieweddocument_state_changedTen minste een ontvanger het die dokument oopgemaak.
envelope.completeddocument_completedAlle ontvangers het geteken — die goue seel is toegeken.
envelope.declineddocument_state_changedEen ontvanger het geweier om te teken.
envelope.voideddocument_state_changedDie dokument is deur die stuurder nietig verklaar.
envelope.expireddocument_state_changedDie tekenvenster het verval sonder voltooiing.
recipient.signedrecipient_completedEen ontvanger het hul handtekening geplaas (voor voltooiing).
recipient.vieweddocument_state_changedEen ontvanger het die skakel vir die eerste keer oopgemaak.

Die wire event-veld volg die PandaDoc-konvensie sodat bestaande werkstrome sonder veranderinge werk. Die interne gebeurtenisnaam word ook gestuur as internal_event in elke loonvrag.

Loonvrag-struktuur

Geteken POST die volgende JSON-struktuur na jou eindpunt:

jsonPOST body
{
  "event": "document_state_changed",   // or "document_completed" / "recipient_completed"
  "internal_event": "envelope.sent",   // precise internal name for routing
  "occurred_at": "2025-06-08T10:23:45.123Z",
  "data": {
    "id": "env_01hx...",               // Geteken envelope UUID
    "name": "Service Agreement — Acme Corp",
    "status": "document.sent",         // PandaDoc-compatible status string
    "date_created": "2025-06-08T09:00:00.000Z",
    "date_modified": "2025-06-08T10:23:44.000Z",
    "metadata": {
      "external_ref": "CRM-4821",      // your correlation ID (see Metadata section)
      "tags": ["sales", "q2"]
    },
    "recipients": [
      {
        "email": "jane@example.com",
        "first_name": "Jane",
        "last_name": "Smith",
        "role": "signer",
        "signing_order": 1,
        "status": "signed",
        "signed_date": "2025-06-08T10:23:44.000Z",
        "first_viewed": "2025-06-08T09:15:00.000Z",
        "declined_date": null
      }
    ]
  }
}

Status-waardes

Die data.status-veld gebruik PandaDoc-konvensie-snare:

document.draft
document.sent
document.viewed
document.completed
document.rejected
document.voided

Ontvanger-status

Elke item in recipients[] dra n status-veld:

pendingsentviewedsigneddeclinedexpired

HMAC-handtekening

Elke versoek dra n X-Geteken-Signature-kop. Die waarde is sha256=hex — n HMAC-SHA256 van die rou versoekliggaam (UTF-8 geenkodeer) gemerk met die webhooksgeheim wat jy in die dashboard gestoor het.

Verifieer altyd die handtekening

Verwerp enige versoek waarvan die handtekening nie ooreenstem nie. Gebruik tydsgelyk-gelyke vergelyking (sien voorbeelde hieronder) om tydsaanvalle te voorkom.

httpRequest headers (example)
POST /webhooks/geteken HTTP/1.1
Host: api.your-app.com
Content-Type: application/json
X-Geteken-Signature: sha256=3a9b2c1d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789
X-Signing-Event: envelope.completed

Handtekening verifieer

Edge / Cloudflare Workers (Web Crypto)

typescriptedge-verification.ts
/**
 * Verify a Geteken webhook request using the Web Crypto API.
 * Works on Cloudflare Workers, Deno, and Node 18+ (no 'node:crypto').
 */
export async function verifyGetekenSignature(
  rawBody: string,
  signatureHeader: string,  // value of X-Geteken-Signature
  secret: string,           // your webhook secret from the dashboard
): Promise<boolean> {
  const prefix = 'sha256=';
  if (!signatureHeader.startsWith(prefix)) return false;

  const hexSig = signatureHeader.slice(prefix.length).toLowerCase().trim();

  // Parse hex signature into bytes
  if (hexSig.length % 2 !== 0) return false;
  const sigBytes = new Uint8Array(hexSig.length / 2);
  for (let i = 0; i < sigBytes.length; i++) {
    sigBytes[i] = parseInt(hexSig.slice(i * 2, i * 2 + 2), 16);
  }

  // Import the secret key
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey(
    'raw',
    enc.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );

  // Compute expected HMAC
  const expected = new Uint8Array(
    await crypto.subtle.sign('HMAC', key, enc.encode(rawBody)),
  );

  // Constant-time comparison — prevents timing attacks
  if (expected.length !== sigBytes.length) return false;
  let diff = 0;
  for (let i = 0; i < expected.length; i++) diff |= expected[i] ^ sigBytes[i];
  return diff === 0;
}

// Example: Cloudflare Workers / Next.js Edge Route
export async function POST(req: Request) {
  const rawBody = await req.text();
  const sig = req.headers.get('x-geteken-signature') ?? '';
  const secret = process.env.GETEKEN_WEBHOOK_SECRET ?? '';

  if (!(await verifyGetekenSignature(rawBody, sig, secret))) {
    return new Response('Forbidden', { status: 403 });
  }

  const event = JSON.parse(rawBody);
  // handle event.event / event.internal_event …
  return new Response('OK', { status: 200 });
}

Node.js (crypto module)

javascriptnode-verification.js
const crypto = require('node:crypto');

function verifyGetekenSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader?.startsWith('sha256=')) return false;

  const receivedHex = signatureHeader.slice(7);
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');

  // timingSafeEqual needs Buffer of equal length
  try {
    return crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(receivedHex, 'hex'),
    );
  } catch {
    return false;  // length mismatch
  }
}

// Express middleware example
app.post('/webhooks/geteken', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-geteken-signature'] ?? '';
  if (!verifyGetekenSignature(req.body.toString('utf8'), sig, process.env.GETEKEN_WEBHOOK_SECRET)) {
    return res.status(403).send('Forbidden');
  }
  const event = JSON.parse(req.body);
  console.log(event.event, event.data.id);
  res.sendStatus(200);
});

Herleweringsbeleid

Eerste poging

Onmiddellik by gebeurtenis

Maksimum pogings

5

Uitstel-skedule

1 min → 5 min → 30 min → 2 h → 6 h

Sukses-kodes

HTTP 200–299

Enige HTTP-antwoord buite die 200-299-reeks (insluitend verbindingsfoute en tyduitputting) word as n mislukking behandel. Geteken herlewer outomaties tot vyf keer. Aflewerings word in die dashboard se Webhook-aansig aangemeld.

Wees idempotent

Jou eindpunt kan dieselfde aflewering meer as een keer ontvang weens herleweringe. Gebruik data.id (die koevert-UUID) saam met internal_event as n idempotensie-sleutel.

Metadata-korrelasie

Wanneer jy n dokument skep via die REST API, kan jy n metadata.external_ref-veld instel. Hierdie waarde verskyn onveranderd in elke webhook-loonvrag — dit is die vinnigste manier om n Geteken-koevert aan n rekord in jou stelsel te koppel.

jsonPOST /public/v1/documents — request body
{
  "name": "Service Agreement — Acme Corp",
  "template_uuid": "tpl_01hx...",
  "recipients": [
    { "email": "jane@example.com", "role": "signer" }
  ],
  "metadata": {
    "external_ref": "CRM-4821",   // your own ID; echoed on every webhook
    "deal_stage": "closing",
    "tags": ["sales", "q2"]
  }
}

Wanneer die koevert se status verander, sal jou eindpunt die volgende ontvang:

jsonWebhook payload (excerpt)
{
  "event": "document_completed",
  "data": {
    "id": "env_01hx...",
    "metadata": {
      "external_ref": "CRM-4821",   // still here, unchanged
      "deal_stage": "closing",
      "tags": ["sales", "q2"]
    }
  }
}

Eindpunte konfigureer

Besoek Account → Webhooks in die dashboard om eindpunte by te voeg, te bewerk of te deaktiveer. Elke eindpunt het:

  • Eindpunt-URLDie HTTPS-bestemming — HTTP word nie aanvaar nie.
  • Ingetekende gebeureKies spesifieke interne gebeurtenisnaamie of teken in op * (almal).
  • WebhooksgeheimOutomaties gegenereer by skepping. Jy kan dit te eniger tyd herrol — nuwe versoeke gebruik onmiddellik die nuwe geheim.
  • Aktief / onaktiefDeaktiveer n eindpunt sonder om dit te verwyder. Geen aflewerings word gestuur terwyl onaktief nie.

Gereed om te integreer?

Voeg jou eerste webhook by in minder as n minuut.