- Markenrecht-Monitoring (Python) — täglich URLs capturen - Webhook-Receiver (Node.js Express) — capture.ready Events archivieren - WordPress-Plugin (PHP) — Captures aus dem WP-Backend
96 lines
3.3 KiB
JavaScript
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);
|
|
}
|