Developers

Alert webhooks

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.

Quick start

  1. In RevHost, open Settings → Alerts and find Send alerts to your own tools.
  2. Choose Add Webhook, paste your https URL, and pick the alerts to send. For location alerts you can narrow it to specific locations, and to specific vehicles for any alert.
  3. Copy the signing secret. It starts with whsec_ and is shown only once.
  4. Choose Send Test Event. RevHost posts an 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.

The request

Each alert is one HTTPS POST with a JSON body. The headers follow Standard Webhooks, so any of its verifier libraries works.

HeaderValue
webhook-idmsg_<alertId>. The same on every retry of one alert: use it to drop duplicates.
webhook-timestampUnix seconds when this attempt was sent (not when the alert happened).
webhook-signaturev1,<base64 HMAC-SHA256>. Two space-separated signatures for 24 hours after you roll the secret.
content-typeapplication/json
user-agentRevHost-Webhooks/1
Headers (example)
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=
Body: alert.created
{
  "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 }
  }
}
id
Same as the webhook-id header.
type
alert.created for real alerts, alert.test for the Send Test Event button.
data.alert.type
The alert type, e.g. GEOFENCE_ENTER. See the list below.
data.alert.typeLabel
The name RevHost shows for it, e.g. “Arrived at the location”.
data.alert.severity
critical, warning or info.
data.alert.eventTime
When it happened on the vehicle. Order events by this, not by arrival.
data.alert.location
Where the vehicle was, when the device reported a position. Can be null.
data.vehicle.name
Your nickname for the car, or “year make model” when it has none.
data.geofence
Only on location alerts: the location’s id, name, and enter, exit or cross.
data.reservation
The trip the car was on when it fired, or null. Only the id: no guest name, phone or email is ever sent.

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.

Verify the signature

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.

verify.js (Node 18+)
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))
    );
  });
}
Using it with Express
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));
});
  • The key is the part of the secret after whsec_, base64-decoded. The signed text is id.timestamp.body.
  • After you choose Roll Secret, requests carry signatures from both the old and the new secret for 24 hours. Accept a request if any signature matches, and update your secret within that window.
  • Compare in constant time, as the snippet does.
  • To test your verifier, the example headers and body above are genuinely signed with the example secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw. It belongs to no account. Set your clock to the example timestamp, or skip the 5-minute check, when you try it.

Delivery, retries and duplicates

Any 2xx answer within 10 seconds counts as delivered. Answer first and do slow work afterwards. Redirects are not followed.

AttemptSent
1Immediately
230 seconds later
32 minutes later
410 minutes later
530 minutes later
61 hour later
73 hours later
86 hours later
  • A failed attempt is retried on the schedule above, about 10 hours end to end. A 400, 401, 403, 404 or 410, or a redirect, means the URL itself is wrong and is not retried. Timeouts, 408, 429 and 5xx are.
  • At least once, not exactly once. You can receive the same alert more than once. Drop repeats by webhook-id.
  • Order is not guaranteed. A retried “Arrived” can land after the next “Left”. Order by data.alert.eventTime.
  • Alerts that reach RevHost more than 2 hours after they happened, such as a device catching up after being offline, are recorded in RevHost but not sent.
  • After 20 failed attempts in a row and no success for 24 hours, RevHost pauses the webhook and emails you. Fix the URL, then resume it in Settings.
  • View Deliverieslists the last 50 deliveries, with each attempt’s status code and error.

Recipe: location alerts to Telegram with n8n

Post “arrived” and “left” for one lot into a Telegram chat. Five minutes, no code.

  1. In n8n, add a Webhook node. Set the method to POST and copy its production URL. Activate the workflow so that URL is live.
  2. In RevHost, add a webhook with that URL. Tick Arrived at the location and Left the location, and under Which locations? choose your lot. Send a test event and check it arrives in n8n.
  3. Optional, if one webhook carries several locations: add an If node that compares {{ $json.body.data.geofence.name }}with the location’s name.
  4. Add a Telegram node, operation Send Message, with your bot credentials and chat ID, and this text:
Telegram message text
{{ $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.

Alert types

Choose any of these per webhook. data.alert.type carries the value on the left.

FAULT_CODEFault code
OVERSPEEDOverspeeding
GEOFENCE_EXITLeft the location
GEOFENCE_ENTERArrived at the location
COLLISIONPossible collision
TAMPER_MOVEMENTMoved while parked
SOSSOS pressed
DRIVER_BEHAVIORDriver behavior
HARSH_ACCELERATIONHarsh acceleration
HARD_BRAKINGHard braking
SHARP_TURNSharp turn
IDLINGIdling
GEOFENCE_CROSSCrossed a line
KILL_SWITCHKill switch
LOW_BATTERYLow battery
POWER_CUTOFFGPS device unplugged
POWER_RESTOREDGPS device reconnected
VIN_MISMATCHWrong vehicle
DEVICE_OFFLINEGPS device offline

Managing webhooks through the API

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.

  • GET /v1/webhook-endpoints
  • POST /v1/webhook-endpoints { url, description?, eventTypes, geofenceIds?, vehicleIds? }
  • PATCH /v1/webhook-endpoints/:id { …same fields, status: "active" | "disabled" }
  • DELETE /v1/webhook-endpoints/:id
  • POST /v1/webhook-endpoints/:id/rotate-secret
  • POST /v1/webhook-endpoints/:id/test
  • GET /v1/webhook-endpoints/:id/deliveries

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.