Get each vehicle alert in your own tools the moment it happens. RevHost sends a signed JSON POST to a URL you choose, for n8n, Zapier, Make or your own server.
whsec_ and is shown only once.alert.test event and shows what your URL answered.Webhooks are free on every plan. Each account can have up to 5. The account owner and their Admins can manage them; Workers cannot.
A webhook gets every alert you chose for it, even ones you have muted or switched off for yourself. Muting is about who gets interrupted; to stop an alert reaching your tool, take it off the webhook.
Each alert is one HTTPS POST with a JSON body. The headers follow Standard Webhooks, so any of its verifier libraries works.
| Header | Value |
|---|---|
| webhook-id | msg_<alertId>. The same on every retry of one alert: use it to drop duplicates. |
| webhook-timestamp | Unix seconds when this attempt was sent (not when the alert happened). |
| webhook-signature | v1,<base64 HMAC-SHA256>. Two space-separated signatures for 24 hours after you roll the secret. |
| content-type | application/json |
| user-agent | RevHost-Webhooks/1 |
POST /your/webhook/path HTTP/1.1
content-type: application/json
user-agent: RevHost-Webhooks/1
webhook-id: msg_6a1f4c2e9b3d7a0012ab34cd
webhook-timestamp: 1793113451
webhook-signature: v1,iflNW+lSq7GcnzHxVHUMuN5HeBJ/e6ICOw44xXWJuug={
"id": "msg_6a1f4c2e9b3d7a0012ab34cd",
"type": "alert.created",
"version": 1,
"createdAt": "2026-10-27T15:04:11.000Z",
"data": {
"alert": {
"id": "6a1f4c2e9b3d7a0012ab34cd",
"type": "GEOFENCE_ENTER",
"typeLabel": "Arrived at the location",
"severity": "warning",
"message": "Vehicle arrived at Home Lot - Pompano (returns)",
"eventTime": "2026-10-27T15:03:52.000Z",
"location": { "lat": 26.2379, "lng": -80.1248 },
"mapsUrl": "https://www.google.com/maps?q=26.2379,-80.1248",
"dashboardUrl": "https://www.revhosthq.com/telematics/alerts?show=all&type=GEOFENCE_ENTER"
},
"vehicle": {
"id": "65b2a1d0c3e4f50011223344",
"name": "Blue Model 3",
"make": "Tesla",
"model": "Model 3",
"year": 2023,
"licensePlate": "ABC1234"
},
"geofence": {
"id": "66c3b2e1d4f5a60022334455",
"name": "Home Lot - Pompano (returns)",
"transition": "enter"
},
"reservation": { "id": "67d4c3f2e5a6b70033445566", "onRental": true }
}
}New fields can be added to dataat any time; ignore the ones you don’t use. A breaking change would come with a new version.
Anyone who learns your URL can post to it. The signature proves a request came from RevHost and was not changed on the way. Check it against the raw body, before parsing it as JSON: re-serialising changes the bytes and the check fails.
import crypto from 'node:crypto';
// rawBody: the request body exactly as received, before any JSON parsing.
export function verifyRevHostWebhook(rawBody, headers, secret) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signatures = (headers['webhook-signature'] ?? '').split(' ');
// Refuse anything older or newer than 5 minutes (replay protection).
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto
.createHmac('sha256', key)
.update(`${id}.${timestamp}.${rawBody}`)
.digest('base64');
return signatures.some((s) => {
const [version, signature = ''] = s.split(',');
return (
version === 'v1' &&
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
);
});
}app.post('/revhost-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
if (!verifyRevHostWebhook(rawBody, req.headers, process.env.REVHOST_WEBHOOK_SECRET)) {
return res.status(401).end();
}
res.status(200).end(); // answer first, then do the work
handleEvent(JSON.parse(rawBody));
});whsec_, base64-decoded. The signed text is id.timestamp.body.whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw. It belongs to no account. Set your clock to the example timestamp, or skip the 5-minute check, when you try it.Any 2xx answer within 10 seconds counts as delivered. Answer first and do slow work afterwards. Redirects are not followed.
| Attempt | Sent |
|---|---|
| 1 | Immediately |
| 2 | 30 seconds later |
| 3 | 2 minutes later |
| 4 | 10 minutes later |
| 5 | 30 minutes later |
| 6 | 1 hour later |
| 7 | 3 hours later |
| 8 | 6 hours later |
webhook-id.data.alert.eventTime.Post “arrived” and “left” for one lot into a Telegram chat. Five minutes, no code.
{{ $json.body.data.geofence.name }}with the location’s name.{{ $json.body.data.alert.typeLabel }}: {{ $json.body.data.vehicle.name }} ({{ $json.body.data.vehicle.licensePlate }})
{{ $json.body.data.geofence.name }}
{{ $json.body.data.alert.mapsUrl }}The test event is alert.test with a sample vehicle and a location called “Home Lot (test)”, so it reaches Telegram unless the If node filters it out. To skip test events everywhere, add an If node on {{ $json.body.type }} equal to alert.createdif you would rather it didn’t. To check signatures in n8n, run the verify code above in a Code node against the request’s raw body.
Choose any of these per webhook. data.alert.type carries the value on the left.
Everything in Settings is also available at https://api.revhosthq.com/v1/webhook-endpoints, with a Personal Access Token that has the webhook:write permission. A token without it is refused, even for reading.
The secret is in the response to create and rotate-secret only. RevHost emails the account owner whenever a webhook is added, its URL changes, or it is resumed.