# Guide to High Mobility Webhooks: Features and Best Practices

High Mobility connects fleet management, insurance and mobility apps to data from vehicles built by many different carmakers. Before an app can read a vehicle's data, the vehicle has to be cleared for the app through a process that differs between carmakers and can take some time. High Mobility webhooks tell you when that clearance is approved, rejected or revoked, so your app can start pulling data, or warn the fleet manager, without polling.

The sections below cover setting up a High Mobility webhook, the two events it sends, checking `X-HM-Signature-256`, and building for a retry policy that can keep resending for more than a day.

## What are High Mobility webhooks?

Each High Mobility app has one webhook configuration with a URL, a secret you choose, and the events to send. The secret belongs to the webhook configuration, which is tied to a project. You can reuse a secret across projects, but High Mobility recommends a unique one for each.

There are only two events today. `fleet_clearance_changed` reports the outcome of a clearance, and `ping` confirms the configuration whenever it is created or changed. All webhooks work with the emulators, but which carmakers send clearance events in production varies.

## High Mobility webhook features

| Feature | Details |
| --- | --- |
| Signature | hex HMAC-SHA256 |
| Signature header | `x-hm-signature-256` |
| Body | JSON |
| Event routing | Body field `event.type` |

Delivery details from High Mobility's [webhook documentation](https://docs.high-mobility.com/docs/webhooks):

* Only HTTPS URLs are accepted.
* Any `2xx` is success, and no response body is needed.
* Connect and response timeouts are 10 seconds each.
* Failed deliveries are retried every 10 minutes, up to 200 attempts in production and 5 in the sandbox.
* `X-HM-Delivery` identifies a delivery and is repeated on its retries.
* Events can be duplicated or arrive out of order, even for the same VIN.
* There is no fixed set of source IP addresses.

## Events and payloads

| Event | Triggered When | Common Use Cases |
| --- | --- | --- |
| `fleet_clearance_changed` | A fleet vehicle's clearance changes | Track onboarding status, surface rejections (`event.action` = `rejected`, `event.detail` = reason) |
| `ping` | The webhook configuration changes | Confirm the endpoint is reachable and verifying correctly |

`fleet_clearance_changed` fires for approvals, rejections and revocations. Intermediate states such as revoking and canceling don't trigger it. `ping` is a normal signed delivery, not a challenge, and its `vehicle.vin` is `*`. High Mobility saves the configuration whether or not the ping succeeds, but your endpoint should still return `2xx`.

This is the `fleet_clearance_changed` sample from the [webhook samples library](https://github.com/hookdeck/webhook-samples/blob/main/providers/high-mobility/latest/fleet_clearance_changed.json):

```json
{
  "vehicle": {
    "vin": "1HMCF6112HA3FBBCC"
  },
  "event": {
    "type": "fleet_clearance_changed",
    "action": "rejected",
    "detail": "invalid VIN",
    "received_at": "2025-06-19T09:49:08.386159Z"
  },
  "application": {
    "id": "A77294AC8DA324FB46DA98921"
  }
}

```

`event.action` holds the new clearance state and `event.detail` the reason, when there is one. `event.received_at` is when High Mobility received the change, which helps when deliveries arrive out of order. `application.id` identifies the app, useful if several apps share one receiver.

## Setting up High Mobility webhooks

1. In the High Mobility console, go to **Apps**, open your app and select **API Credentials**, then **Webhooks**.
2. Enter the HTTPS **URL** of your receiver, for example `https://example.com/webhooks/high-mobility`.
3. Enter a **secret**: a random, high-entropy string you generate. Store the same value in your server's configuration as `HM_WEBHOOK_SECRET`, exactly as entered.
4. Choose the events, then click **Update**. High Mobility sends a `ping`.

To use Hookdeck, create a High Mobility source with the same secret as its verification secret, and enter the source URL in step 2.

### HTTP Method

High Mobility's docs don't name the HTTP method. Hookdeck's High Mobility source pre-selects POST without managing the method, so any other method the sender uses must also be allowed on the source. The linked skill examples accept `GET`, `POST`, `PUT`, `PATCH` and `DELETE`.

### Test a delivery

Forward events to a local server with the [Hookdeck CLI](/docs/cli):

```bash
npx hookdeck-cli listen 3000 high-mobility --path /webhooks/high-mobility

```

Saving the webhook configuration sends a `ping`, and sandbox clearance events exercise the rest of your handler.

## Securing High Mobility webhooks

`X-HM-Signature-256` is `sha256=` followed by a hex HMAC-SHA256 of the exact request body, keyed with your secret. Strip the prefix, compute the HMAC over the raw bytes, and compare in constant time. High Mobility still sends a SHA-1 `X-HM-Signature` header for older integrations, but describes it as deprecated, so ignore it.

### Node.js verification

```javascript
const crypto = require('node:crypto');

function verifySignature(rawBody, headers, secret) {
  if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
  const signature = headers['x-hm-signature-256'];
  if (typeof signature !== 'string') return false;
  const key = Buffer.from(secret, 'utf8');
  const message = rawBody;
  const candidates = [signature.startsWith('sha256=') ? signature.slice(7) : signature];
  const expected = crypto.createHmac('sha256', key).update(message).digest();
  return candidates.some((candidate) => {
    if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
    const received = Buffer.from(candidate, 'hex');
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

```

The receiver accepts any method, as the HTTP Method section explains, and handles `ping` before looking for a vehicle:

```javascript
const express = require('express');

const app = express();

app.all('/webhooks/high-mobility', express.raw({ type: '*/*' }), async (req, res) => {
  const secret = process.env.HM_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500);
  const body = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
  if (!verifySignature(body, req.headers, secret)) return res.sendStatus(401);
  if (!body.length) return res.sendStatus(200);

  let payload;
  try {
    payload = JSON.parse(body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return res.sendStatus(400);
  try {
    if (payload.event?.type === 'ping') return res.sendStatus(200);

    if (payload.event?.type === 'fleet_clearance_changed') {
      await recordClearanceChange(req.get('x-hm-delivery'), {
        vin: payload.vehicle?.vin,
        state: payload.event.action,
        reason: payload.event.detail ?? null,
        receivedAt: payload.event.received_at,
      });
    }
  } catch {
    return res.sendStatus(503);
  }

  res.sendStatus(200);
});

```

`recordClearanceChange` skips a delivery ID it has already stored. Before acting on a clearance, compare `receivedAt` with the latest one stored for the VIN, or fetch the current clearance from the API, since an older change can arrive after a newer one.

Nothing time-based is signed, so verification can't tell an old request from a new one. Hookdeck's High Mobility source checks the same `sha256=` HMAC with no age limit, returns `200` to High Mobility, and records a signature mismatch as failed verification.

> **Make High Mobility webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies each clearance event, answers well within the 10-second timeout, and gives you a searchable history of every clearance change per VIN.

## High Mobility webhook limitations and pain points

### Retries run for over a day

**The Problem:** One failing delivery keeps arriving every 10 minutes, and old clearance changes can land long after newer ones.

**Why It Happens:** High Mobility retries every 10 minutes up to 200 attempts in production, which is more than 33 hours. Ordering is not guaranteed.

**Workarounds:**

* Deduplicate on `X-HM-Delivery`.
* Compare `event.received_at` with your stored state before applying a change, or check the latest clearance through the API.

**How Hookdeck Can Help:** Hookdeck accepts each delivery the first time, so High Mobility's retry queue stays empty, and you control how often and how long Hookdeck retries your service.

### The sandbox gives up after 5 attempts

**The Problem:** Sandbox events go missing during development when your local server isn't running.

**Why It Happens:** The sandbox retries only up to 5 times in total, 10 minutes apart.

**Workarounds:**

* Keep a tunnel and receiver running while testing clearances.
* Trigger the clearance again if a test event was lost.

**How Hookdeck Can Help:** With the Hookdeck CLI, sandbox events are held by the Hookdeck source while your laptop is offline, and you can replay them when you reconnect.

### The same change can arrive under different IDs

**The Problem:** Deduplicating on `X-HM-Delivery` alone doesn't catch every repeat.

**Why It Happens:** High Mobility notes that separately generated events may represent the same underlying change with different delivery identifiers.

**Workarounds:**

* Treat clearance updates as state, not as actions: applying the same state to a VIN twice should be harmless.
* Key side effects such as emails on VIN plus new state.

**How Hookdeck Can Help:** Hookdeck deduplication can use body fields such as `vehicle.vin`, `event.action` and `event.received_at` to drop repeats within a window you set.

### Coverage depends on the carmaker

**The Problem:** Clearance events you see in the emulator may not arrive for some brands in production.

**Why It Happens:** High Mobility states that production availability differs by carmaker.

**Workarounds:**

* Check availability for the brands in your fleet before relying on webhooks alone.
* Keep a periodic clearance status check as a fallback.

**How Hookdeck Can Help:** Hookdeck's request history shows which VINs and brands are generating events, so gaps stand out.

## Best practices

### Ignore the SHA-1 header

Verify only `X-HM-Signature-256`. The SHA-1 header exists for older integrations.

### Make clearance updates idempotent

Store the latest clearance state per VIN and treat each event as a request to move towards it. The [idempotency guide for webhooks](/webhooks/guides/implement-webhook-idempotency) shows how to record what you've already handled.

### Answer inside 10 seconds

Fetching vehicle data or notifying a fleet manager can be done after the response. See [why webhooks should be processed asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Use a unique secret per project

High Mobility allows sharing a secret between projects but recommends against it. A unique secret limits the damage if one leaks.

## Conclusion

High Mobility webhooks report vehicle clearance outcomes and configuration pings, signed with an HMAC-SHA256 of the body in `X-HM-Signature-256`. Verify the raw body, accept the method High Mobility uses, and treat each clearance as state that can arrive late, twice or out of order during up to 200 retries.

[Hookdeck Event Gateway](https://hookdeck.com) takes those retries off High Mobility's hands and delivers clearance events to your app on your schedule.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle High Mobility webhooks reliably in minutes.