# High Mobility Webhooks

## When to Use This Skill

* How do I receive High Mobility webhooks?
* How do I verify the `X-HM-Signature-256` header?
* How do I handle `fleet_clearance_changed` and `ping` events?
* Why is my High Mobility webhook signature verification failing?
* Why does a `sha256=` prefix break my HMAC comparison?

## Essential Code (USE THIS)

High Mobility sends `X-HM-Signature-256: sha256=<lowercase hex>`. The digest is HMAC-SHA256 over the **raw request body bytes only** (no timestamp, id or other header is signed), keyed with the webhook secret string's UTF-8 bytes **as-is** (not hex- or base64-decoded). High Mobility has no verification SDK, so verify manually.

Verify only `X-HM-Signature-256`. The deprecated `X-HM-Signature` header (HMAC-SHA1) is still sent for old integrations; ignore it, as Hookdeck's source does.

### Verification (Node.js)

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

// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyHighMobility(rawBody, header, secret) {
  if (!secret || typeof header !== 'string') return false;
  let sig = header.trim();
  if (/^sha256=/i.test(sig)) sig = sig.slice('sha256='.length); // bare digest is accepted too
  if (!/^[0-9a-f]{64}$/i.test(sig)) return false;               // validate before comparing
  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex'));
  const received = Buffer.from(sig.toLowerCase());
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

```

### Verification (Python)

```python
import hashlib, hmac, re

def verify_high_mobility(raw_body: bytes, header: str, secret: str) -> bool:
    if not secret or not header:
        return False
    sig = header.strip()
    if sig[:7].lower() == "sha256=":
        sig = sig[7:]                                    # bare digest is accepted too
    if not re.fullmatch(r"[0-9a-fA-F]{64}", sig):        # validate before comparing
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest().encode()
    return hmac.compare_digest(sig.lower().encode(), expected)

```

Respond **401** when the header is missing or empty, the value is malformed, or the signature does not match. No timestamp is signed, so there is no replay window to check; dedupe on your own keys instead (deliveries can repeat and arrive out of order).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/high-mobility-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/high-mobility-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/high-mobility-webhooks/examples/fastapi/).

## HTTP Methods

High Mobility's docs don't name the HTTP method. Hookdeck's High Mobility source pre-selects POST without managing the method (you can change it), so any other method the sender uses must also be allowed on the source. The examples accept GET, POST, PUT, PATCH and DELETE on the route, verify the signature first for every method, and treat a missing body as zero bytes: a verified GET with no body is acknowledged with `200`, not rejected as invalid JSON.

## Common Event Types

The event name is in the body's nested `event.type` field.

| Event | Description |
| --- | --- |
| `fleet_clearance_changed` | A fleet vehicle's clearance changed; `event.action` refines it (for example `rejected`) and `event.detail` gives a reason |
| `ping` | Sent when the webhook configuration changes; a normal signed delivery, not a challenge. `vehicle.vin` is `*` |

After verifying, treat every field of the parsed body as untrusted and check types before reading into it. See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/high-mobility-webhooks/references/overview.md).

> **For the full payload reference**, see [High Mobility webhooks](https://docs.high-mobility.com/docs/webhooks).

## Important Headers

| Header | Description |
| --- | --- |
| `X-HM-Signature-256` | `sha256=<lowercase hex HMAC-SHA256 of the raw body>` |
| `X-HM-Signature` | Deprecated HMAC-SHA1 signature; do not use |

## Environment Variables

```bash
HIGH_MOBILITY_WEBHOOK_SECRET=your_webhook_secret   # the secret you entered when configuring the webhook

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 high-mobility --path /webhooks/high-mobility

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/high-mobility-webhooks/references/overview.md) - High Mobility webhook concepts, events and delivery behaviour
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/high-mobility-webhooks/references/setup.md) - Configuring the webhook, secret and Hookdeck source
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/high-mobility-webhooks/references/verification.md) - Signature verification details and gotchas