Recibe notificaciones HTTP en tu servidor cuando ocurren eventos en tu cuenta Kligrafia: firmas completadas, KYC, formularios enviados, etc.
Kligrafia-Signature: t=<ts>,v1=<hmac>.abandoned.POST {tu-url}
content-type: application/json
user-agent: Kligrafia-Webhook/1.0
kligrafia-signature: t=1715000000,v1=a3b4c5...
{
"id": "whkdel_01k...",
"type": "envelope.completed",
"created": 1715000000,
"data": {
"envelopeId": "env_...",
"title": "Contrato XYZ",
"completedAt": "2026-05-12T19:30:00Z",
"signers": [
{ "id": "sgn_...", "email": "[email protected]", "signedAt": "..." }
]
}
}envelope.createdenvelope.sentenvelope.viewedenvelope.signedenvelope.completedenvelope.declinedenvelope.voidedenvelope.expiredsigner.viewedsigner.signedsigner.declinedkyc.verifiedkyc.failedkyc.fraud_detectedpublic_form.submittedPodés suscribir a * para recibir todos los eventos presentes y futuros, o usar prefijos como envelope.*.
El header Kligrafia-Signature tiene el formato t=<timestamp>,v1=<hmac-sha256-hex>. La firma se calcula sobre <t>.<raw-body>.
Tolerancia: 300 segundos contra el timestamp para evitar replay attacks.
// Node.js — verificación de firma
const crypto = require('node:crypto');
function verifySignature(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map(p => p.trim().split('='))
);
const ts = Number(parts.t);
const sig = parts.v1;
if (!ts || !sig) return false;
if (Math.abs(Date.now() / 1000 - ts) > 300) return false; // 5 min tolerance
const expected = crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(sig, 'hex');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
// Express example
app.post('/webhooks/kligrafia', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['kligrafia-signature'];
const ok = verifySignature(req.body.toString('utf-8'), sig, process.env.KLIGRAFIA_WEBHOOK_SECRET);
if (!ok) return res.status(401).send('invalid signature');
const event = JSON.parse(req.body.toString('utf-8'));
// handle event.type === 'envelope.completed' etc.
res.status(200).send('ok');
});# Python — verificación de firma
import hmac, hashlib, time
def verify_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split('=', 1) for p in header.split(','))
ts = int(parts.get('t', 0))
sig = parts.get('v1', '')
if not ts or not sig:
return False
if abs(time.time() - ts) > tolerance:
return False
expected = hmac.new(
secret.encode(),
f"{ts}.{raw_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, sig)
# Flask example
@app.route('/webhooks/kligrafia', methods=['POST'])
def kligrafia_webhook():
sig = request.headers.get('Kligrafia-Signature', '')
if not verify_signature(request.get_data(), sig, os.environ['KLIGRAFIA_WEBHOOK_SECRET']):
return 'invalid signature', 401
event = request.get_json()
# handle event
return 'ok', 200<?php
// PHP — verificación de firma (compatible con WordPress plugin)
function kligrafia_verify_signature(string $body, string $header, string $secret, int $tolerance = 300): bool {
$parts = [];
foreach (explode(',', $header) as $p) {
[$k, $v] = explode('=', trim($p), 2);
$parts[$k] = $v;
}
$ts = (int) ($parts['t'] ?? 0);
$sig = $parts['v1'] ?? '';
if (!$ts || !$sig) return false;
if (abs(time() - $ts) > $tolerance) return false;
$expected = hash_hmac('sha256', $ts . '.' . $body, $secret);
return hash_equals($expected, $sig);
}<!-- Browser — JS SDK auto-loaded -->
<script src="https://kligrafia.com/sdk/v1/kligrafia.js"></script>
<script>
// Embed form en un container existente
Kligrafia.embedForm({
publicId: 'pubform_abc123',
container: '#kligrafia-mount',
onSubmit: (e) => console.log('signed:', e),
});
// O modal overlay
document.getElementById('cta').onclick = () => Kligrafia.openForm('pubform_abc123');
// Verificar firma (cuando recibes webhooks vía edge function, etc.)
await Kligrafia.verifySignature(rawBody, signatureHeader, secret); // boolean
</script>event.id para deduplicar. Los reintentos pueden entregar el mismo evento más de una vez.