Skip to content
Track That Ride
Menu

API documentation

Webhooks

Webhooks tell your server when something happens in Track That Ride, such as a delivery starting, being delivered or failing, by POSTing JSON to a URL you choose. Every request is signed with HMAC-SHA256 using a secret only you hold, failed deliveries are retried with exponential backoff, and you can send a test event at any time.

Looking for every endpoint and schema in one place? See the Full OpenAPI reference.

Endpoints

Method and path What it does Key permission
GET /api/v1/webhooks List Webhooks checked by the endpoint
POST /api/v1/webhooks Create Webhook checked by the endpoint
GET /api/v1/webhooks/{id} Get a webhook checked by the endpoint
PUT /api/v1/webhooks/{id} Update a webhook checked by the endpoint
DELETE /api/v1/webhooks/{id} Delete a webhook checked by the endpoint
POST /api/v1/webhooks/{id}/test Send a test event to a webhook checked by the endpoint
POST /api/v1/webhooks/{id}/rotate-secret Rotate the signing secret of a webhook checked by the endpoint
POST /api/v1/webhooks/{id}/reveal-secret Show the signing secret of a webhook checked by the endpoint

The number of webhook endpoints you can register depends on your plan.

Registering a webhook

curl -X POST https://trackthatride.com/api/v1/webhooks \
  -H "Authorization: Bearer $TTR_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/ttr",
    "events": ["delivery.started", "delivery.completed", "delivery.failed"],
    "maxRetries": 3,
    "retryBackoffMs": 1000
  }'

url and at least one event are required. maxRetries is 0 to 10 (default 3) and retryBackoffMs 100 to 60,000 (default 1,000). The 201 response includes the signing secret (starting whsec_), shown only this once. Unknown event names are rejected with 400.

Which events to subscribe to

For delivery tracking, these are the useful ones:

EventWhen
ride.createdTriggered when a new ride is created (includes ride_id, tracking_code, status, customer info, addresses)
ride.assignedTriggered when a driver is assigned to a ride (includes ride_id, driver_id, tracking_code)
ride.status_changedLegacy event for status changes (prefer delivery.started/completed/failed)
review.requestedTriggered when a delivery completes and a customer review token is issued
ride.update_sentA customer-facing update was posted to the tracking page: update {id, kind, title, body, data}
ride.update_seenThe customer opened the tracking page and saw updates (update_ids, seen_at)
customer.createdA new customer record was created from a ride (customer_id, name, phone_e164)
delivery.startedTriggered when delivery starts (status: assigned → in_progress)
delivery.completedTriggered when delivery completes successfully
delivery.failedTriggered when delivery fails or is cancelled

delivery.completed also carries the proof of delivery, with links to photos and signatures that are valid for 24 hours, and the timings arrived_at_pickup_at, arrived_at_dropoff_at, distance_travelled_m and duration_s. delivery.failed is also sent for cancellations, with data.cancelled: true.

All 31 accepted event names

ride.created, ride.updated, ride.assigned, ride.status_changed, ride.completed, ride.cancelled, ride.pickup_completed, ride.delivery_attempted, review.requested, ride.update_sent, ride.update_seen, customer.created, delivery.started, delivery.completed, delivery.failed, delivery.delayed, driver.location_updated, driver.status_changed, driver.assigned, driver.unassigned, driver.online, driver.offline, company.approved, company.suspended, company.updated, user.registered, user.verified, user.role_changed, system.maintenance_mode, system.alert_created, system.configuration_changed

Some names are accepted for compatibility but are rarely or never sent; build on the events in the table above.

Request format

Each delivery is an HTTPS POST with these headers:

HeaderValue
Content-Typeapplication/json
User-AgentTrack-That-Ride-Webhooks/1.0
X-Webhook-EventThe event name, e.g. delivery.completed
X-Webhook-Signaturesha256= followed by the hex HMAC of the raw body
X-Webhook-Signature-Versionv1
X-Webhook-TimestampThe event’s ISO timestamp
X-Webhook-IDYour webhook’s ID

The body has this shape (fields vary by event):

{
  "event_type": "delivery.completed",
  "timestamp": "2030-01-15T14:32:10.000Z",
  "company_id": 12,
  "ride_id": 123,
  "driver_id": 45,
  "tracking_code": "<public tracking token>",
  "data": {
    "status": "delivered",
    "previous_status": "in_progress",
    "customer_name": "Jane Example",
    "pickup_address": "1 Example Road, London E1 6AN",
    "delivery_address": "10 Downing Street, London SW1A 2AA"
  },
  "metadata": { "source": "track-that-ride-api", "version": "1.0" }
}

tracking_code is the public tracking token, so https://trackthatride.com/track/ plus that value is the customer’s tracking page.

Verifying the signature

Compute an HMAC-SHA256 of the raw request body (before parsing JSON) with your webhook secret, prefix it with sha256= and compare it with X-Webhook-Signature using a constant-time comparison. In Node.js with Express:

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.TTR_WEBHOOK_SECRET; // whsec_...

app.post('/hooks/ttr', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
  const received = req.get('X-Webhook-Signature') ?? '';
  const ok = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body.toString('utf8'));
  console.log(event.event_type, event.ride_id, event.data.status);
  res.status(200).end(); // reply quickly; do slow work afterwards
});

app.listen(3000);

Retries and timeouts

  • Reply with any 2xx status within 30 seconds; anything else counts as a failure.
  • Failures are retried up to maxRetries times, waiting retryBackoffMs × 2^(attempt − 1), at most five minutes.
  • Retries can deliver the same event twice; use ride_id, event_type and timestamp to ignore duplicates.

Testing and rotating secrets

POST /api/v1/webhooks/{id}/test sends a webhook.test event to your URL. …/rotate-secret issues a new secret (update your server first, then rotate), and …/reveal-secret shows the current one.