# FourKites Webhooks

FourKites (supply chain visibility) POSTs a JSON callback to your URL when something happens to a
load: it is created, a tracking update arrives, a truck arrives at or leaves a stop, an ETA
changes, a stop is completed. The message kind is the `MessageType` field in the body.

Callbacks are **not signed**. FourKites supports three authentication methods, chosen when you
subscribe: Basic Auth, API Key / Auth Header, or OAuth2 (client credentials). **This skill covers
HTTP Basic auth**, which is what Hookdeck's FourKites source verifies. "Standard Webhooks" is
FourKites' licensed product name; it is not the standardwebhooks.com signature spec.

## When to Use This Skill

* How do I receive FourKites webhooks / callbacks?
* How do I authenticate FourKites requests with Basic auth?
* Does FourKites sign webhook payloads? (No: use the shared credential)
* How do I handle `LOAD_CREATION`, `TRACKING_UPDATE`, `STOP_ARRIVAL` or `STOP_ETA_UPDATED`?
* Why does my FourKites webhook endpoint return 401?

## Verification (core)

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

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
const safeEqual = (a, b) =>
  crypto.timingSafeEqual(
    crypto.createHash('sha256').update(a).digest(),
    crypto.createHash('sha256').update(b).digest()
  );

function verifyFourKitesRequest(headers, user = process.env.FOURKITES_WEBHOOK_USERNAME,
                                pass = process.env.FOURKITES_WEBHOOK_PASSWORD) {
  if (!user || !pass) return false;                       // never accept when unconfigured
  const m = /^basic\s+([A-Za-z0-9+/]+={0,2})\s*$/i.exec(headers['authorization'] || '');  // scheme is case-insensitive
  if (!m) return false;
  const decoded = Buffer.from(m[1], 'base64').toString('utf8');
  const i = decoded.indexOf(':');                          // split on the FIRST colon
  if (i < 0) return false;
  const userOk = safeEqual(decoded.slice(0, i), user);     // evaluate both, no short-circuit
  const passOk = safeEqual(decoded.slice(i + 1), pass);
  return userOk && passOk;
}

```

* Header: `Authorization: Basic base64(username:password)`. Return `401` for a missing or malformed
  header or a mismatch, `400` for a body that isn't a JSON object, `200` once handled.
* **The credentials must arrive on the first request.** FourKites' docs don't say whether Basic
  credentials are sent preemptively. A `401` + `WWW-Authenticate` challenge flow won't work through
  Hookdeck, which verifies the request as received.
* **A static shared credential authenticates the sender, not the body, and gives no replay
  protection** (no signature, no timestamp). Anyone who captures a request can resend it. Use
  HTTPS only, make handlers idempotent, and rotate the credential if it leaks.
* Python: `hmac.compare_digest` on **bytes** (encode both sides; `str` raises on non-ASCII).
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it.

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

## Event Types

The body's `MessageType` field selects the event. Every key is present on every message.

| MessageType | Sent When |
| --- | --- |
| `LOAD_CREATION` | FourKites successfully creates a load (carries `FourKitesLoadId`) |
| `LOCATION_UPDATE` | A location update is recorded for a load |
| `TRACKING_UPDATE` | Key tracking updates are made to the load (`Status`, `Message`) |
| `STOP_ETA_UPDATED` | A stop's ETA changes |
| `STOP_ARRIVAL` | The truck arrives at a stop |
| `STOP_DEPARTURE` | The truck departs a stop |
| `STOP_AUTO_COMPLETED` | A stop is completed automatically |
| `MANUALLY_DELIVERED` | A load is manually marked delivered |
| `MANUALLY_PICKED_UP` | A load is manually marked picked up |
| `STOP_APPOINTMENT_RESCHEDULED` | A stop appointment is rescheduled |
| `STOP_AMEND` | A stop is amended |
| `LOAD_DELETED` | A load is deleted |

More messages exist for orders, documents and facilities; see the
[FourKites API reference](https://docs.fourkites.com/fourkites-api-reference/webhooks).

## Environment Variables

```bash
FOURKITES_WEBHOOK_USERNAME=your-username   # supplied to FourKites when subscribing
FOURKITES_WEBHOOK_PASSWORD=your-password

```

## Local Development

FourKites can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 fourkites --path /webhooks/fourkites

```

Give FourKites the printed URL and the Basic credentials.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fourkites-webhooks/references/overview.md) - What FourKites callbacks are, events, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fourkites-webhooks/references/setup.md) - Subscribe a URL and choose Basic auth
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fourkites-webhooks/references/verification.md) - Basic auth check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/fourkites-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests