-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebhook-server.mjs
More file actions
55 lines (48 loc) · 2.01 KB
/
Copy pathwebhook-server.mjs
File metadata and controls
55 lines (48 loc) · 2.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* A webhook receiver, with no framework.
*
* export VISION_WEBHOOK_SECRET=whsec_… # dashboard → Webhooks
* node examples/webhook-server.mjs
*
* The endpoint must be HTTPS and must not resolve to a private address, so a bare
* localhost receiver is unreachable by design — put a tunnel (ngrok, cloudflared) in
* front of it while developing.
*/
import { createServer } from 'node:http';
import { verifyWebhook, WebhookSignatureError } from '@devrobotlabs/visionapi';
const SECRET = process.env.VISION_WEBHOOK_SECRET;
if (!SECRET) throw new Error('Set VISION_WEBHOOK_SECRET (dashboard → Webhooks).');
createServer((req, res) => {
if (req.method !== 'POST' || req.url !== '/hooks/vision') {
res.writeHead(404).end();
return;
}
// Collect the raw bytes. The signature covers exactly what arrived — parsing first and
// re-serializing changes key order and whitespace, and the HMAC stops matching.
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => {
const body = Buffer.concat(chunks);
let event;
try {
event = verifyWebhook(body, req.headers['x-vision-signature'], SECRET);
} catch (err) {
if (err instanceof WebhookSignatureError) {
console.warn('rejected delivery:', err.message);
res.writeHead(400).end();
return;
}
throw err;
}
// Any 2xx is success. Acknowledge immediately and do the work afterwards — a slow
// handler looks like a failed delivery and earns a retry at +1 m, +5 m, +15 m, +40 m.
res.writeHead(202).end();
const delivery = req.headers['x-vision-delivery'];
if (event.event === 'task.failed') {
console.error(`task ${event.task_id} failed: ${event.error?.code} (delivery ${delivery})`);
return;
}
console.log(`task ${event.task_id} completed — ${event.credits_used} credits`);
console.log(JSON.stringify(event.result).slice(0, 300));
});
}).listen(3000, () => console.log('listening on http://localhost:3000/hooks/vision'));