examples/webhook-receiver/server.mjs
Stefan Schmidt-Egermann 64e9f1144b
feat: initial examples content (v0.1.0)
- Markenrecht-Monitoring (Python) — täglich URLs capturen
- Webhook-Receiver (Node.js Express) — capture.ready Events archivieren
- WordPress-Plugin (PHP) — Captures aus dem WP-Backend
2026-04-25 12:26:09 +02:00

96 lines
3.3 KiB
JavaScript

// hightrusted CAPTURE — Webhook Receiver (Express)
//
// Empfängt capture.ready / capture.failed Events, verifiziert die HMAC-Signatur,
// lädt das fertige PDF herunter und legt es im Archiv ab.
//
// Use-Case:
// Du sendest Capture-Anfragen mit mode=webhook. Statt zu pollen, bekommst du
// den fertigen Capture per HTTP-POST geliefert. Dieser Server nimmt das
// entgegen, validiert und archiviert.
//
// Setup:
// npm install express @hightrusted/capture
// export HIGHTRUSTED_API_KEY=ht_live_...
// export HIGHTRUSTED_WEBHOOK_SECRET=wh_secret_...
// node server.mjs
import express from 'express';
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { Client, verifyWebhookSignature } from '@hightrusted/capture';
const PORT = process.env.PORT ?? 8000;
const WEBHOOK_SECRET = process.env.HIGHTRUSTED_WEBHOOK_SECRET;
const ARCHIVE_DIR = process.env.ARCHIVE_DIR ?? './archiv';
if (!WEBHOOK_SECRET) {
console.error('HIGHTRUSTED_WEBHOOK_SECRET fehlt — siehe Dashboard.');
process.exit(1);
}
const client = new Client(); // API-Key aus ENV
const app = express();
// WICHTIG: raw body brauchen wir für die HMAC-Verifikation.
// Express muss den Body als Buffer durchreichen, nicht parsen.
app.post(
'/webhooks/capture',
express.raw({ type: 'application/json', limit: '1mb' }),
async (req, res) => {
const signature = req.header('X-Hightrusted-Signature') ?? '';
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
console.warn('[%s] Signature mismatch', new Date().toISOString());
return res.status(401).json({ error: 'invalid_signature' });
}
let payload;
try {
payload = JSON.parse(req.body.toString('utf-8'));
} catch {
return res.status(400).json({ error: 'invalid_json' });
}
const { event, capture } = payload;
console.log('[%s] %s — %s', new Date().toISOString(), event, capture?.id);
// Sofort 200 — der Rest läuft asynchron, sonst rennt der Webhook in Timeout
res.status(200).json({ ok: true });
if (event === 'capture.ready') {
try {
await archiveCapture(capture);
} catch (err) {
console.error('Archive-Fehler:', err);
}
} else if (event === 'capture.failed') {
console.error('Capture failed: ref=%s reason=%s', capture.reference, payload.error?.code);
// → hier: Mandant benachrichtigen, ggf. retry
}
}
);
app.get('/health', (_req, res) => res.json({ ok: true }));
app.listen(PORT, () => {
console.log(`Webhook-Receiver lauscht auf :${PORT}/webhooks/capture`);
});
// ─────────────────────────────────────────────────────────────────
async function archiveCapture(capture) {
const date = new Date(capture.timestamp.issued_at).toISOString().slice(0, 10);
const dir = join(ARCHIVE_DIR, date);
await mkdir(dir, { recursive: true });
const ref = capture.reference ?? capture.id.slice(0, 8);
const safe = ref.replace(/[^a-zA-Z0-9_-]/g, '_');
const pdfPath = join(dir, `${safe}_${capture.id.slice(0, 8)}.pdf`);
await client.downloadPdf(capture.id, pdfPath);
await writeFile(
`${pdfPath}.json`,
JSON.stringify(capture, null, 2),
'utf-8'
);
console.log(' → archiviert: %s', pdfPath);
}