Vinnige begin
- Gaan na
Account → Webhooksin die dashboard en voeg jou eindpunt-URL by. - Kies die
gebeurewaarop jy wil inteken (of teken in op almal). - Kopieer jou
webhooksgeheimen stoor dit veilig — dit word eenmalig gewys. - Bevestig elke inkomende versoek deur die
X-Geteken-Signaturekop te verifieer.
Gebeure
Elke webhook-aflewering dra n top-vlak event-veld. Hier is al die gebeure waarop jy kan inteken:
| Interne gebeurtenisnaam | Wire event-waarde | Beskrywing |
|---|---|---|
| envelope.sent | document_state_changed | Dokument is aan alle ontvangers gestuur. |
| envelope.viewed | document_state_changed | Ten minste een ontvanger het die dokument oopgemaak. |
| envelope.completed | document_completed | Alle ontvangers het geteken — die goue seel is toegeken. |
| envelope.declined | document_state_changed | Een ontvanger het geweier om te teken. |
| envelope.voided | document_state_changed | Die dokument is deur die stuurder nietig verklaar. |
| envelope.expired | document_state_changed | Die tekenvenster het verval sonder voltooiing. |
| recipient.signed | recipient_completed | Een ontvanger het hul handtekening geplaas (voor voltooiing). |
| recipient.viewed | document_state_changed | Een 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:
{
"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:
Ontvanger-status
Elke item in recipients[] dra n status-veld:
pendingsentviewedsigneddeclinedexpiredHMAC-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.
POST /webhooks/geteken HTTP/1.1
Host: api.your-app.com
Content-Type: application/json
X-Geteken-Signature: sha256=3a9b2c1d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789
X-Signing-Event: envelope.completedHandtekening verifieer
Edge / Cloudflare Workers (Web Crypto)
/**
* 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)
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.
{
"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:
{
"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-URL — Die HTTPS-bestemming — HTTP word nie aanvaar nie.
- Ingetekende gebeure — Kies spesifieke interne gebeurtenisnaamie of teken in op * (almal).
- Webhooksgeheim — Outomaties gegenereer by skepping. Jy kan dit te eniger tyd herrol — nuwe versoeke gebruik onmiddellik die nuwe geheim.
- Aktief / onaktief — Deaktiveer 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.