-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebhook.js
More file actions
75 lines (63 loc) · 2.54 KB
/
Copy pathwebhook.js
File metadata and controls
75 lines (63 loc) · 2.54 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
'use strict';
/**
* Express receiver for Vito webhooks.
*
* Webhooks are the instant, signature-verified completion path. Point your
* project's callbackUrl (in the Vetox dashboard) at `<public-url>/webhook/vito`.
* See docs/VITO_API.md for the event types and signature scheme.
*
* `onEvent(eventType, data, verified)` is invoked for every ACCEPTED delivery.
* It must never throw back into the HTTP response — we always ack quickly so the
* API marks the delivery successful and does not needlessly retry.
*/
const express = require('express');
const { config } = require('./config');
const { verifyWebhookSignature } = require('./vito');
function createWebhookServer(onEvent) {
const app = express();
// Capture the raw body so the HMAC is computed over the EXACT received bytes.
// Re-serialising req.body would change whitespace/key order and break the HMAC.
app.use(
express.json({
verify: (req, _res, buf) => {
req.rawBody = buf.toString('utf8');
},
}),
);
// Simple health check — handy for uptime probes and tunnel sanity checks.
app.get('/', (_req, res) => res.json({ ok: true, service: 'vito-demo-bot' }));
app.post('/webhook/vito', (req, res) => {
const sigHeader = req.get('X-Vito-Signature') || '';
const eventType = req.get('X-Vito-Event-Type') || req.body?.eventType || '';
const data = req.body?.data || {};
// Secure by default: without a signing secret we cannot trust the delivery,
// so we ack it (to stop retries) but do NOT act on it — delivering a key off
// an unverifiable webhook would be a free-product forgery. Set VITO_WEBHOOK_KEY
// to enable the webhook completion path; polling still delivers meanwhile.
if (!config.webhookSecret) {
console.warn(
'[webhook] Ignored — VITO_WEBHOOK_KEY not set, cannot verify. Set it to enable webhook completion.',
);
return res.json({ ok: true });
}
const verified = verifyWebhookSignature(
config.webhookSecret,
sigHeader,
req.rawBody || '',
config.webhookToleranceSec,
);
if (!verified) {
console.warn('[webhook] ✋ Rejected — bad or stale signature for', eventType);
return res.status(401).json({ ok: false, error: 'invalid signature' });
}
try {
onEvent(eventType, data, true);
} catch (err) {
console.warn('[webhook] handler error:', err.message);
}
// Always ack quickly so the API marks the delivery successful.
res.json({ ok: true });
});
return app;
}
module.exports = { createWebhookServer };