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:
| Event | When |
|---|---|
ride.created | Triggered when a new ride is created (includes ride_id, tracking_code, status, customer info, addresses) |
ride.assigned | Triggered when a driver is assigned to a ride (includes ride_id, driver_id, tracking_code) |
ride.status_changed | Legacy event for status changes (prefer delivery.started/completed/failed) |
review.requested | Triggered when a delivery completes and a customer review token is issued |
ride.update_sent | A customer-facing update was posted to the tracking page: update {id, kind, title, body, data} |
ride.update_seen | The customer opened the tracking page and saw updates (update_ids, seen_at) |
customer.created | A new customer record was created from a ride (customer_id, name, phone_e164) |
delivery.started | Triggered when delivery starts (status: assigned → in_progress) |
delivery.completed | Triggered when delivery completes successfully |
delivery.failed | Triggered 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:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Track-That-Ride-Webhooks/1.0 |
X-Webhook-Event | The event name, e.g. delivery.completed |
X-Webhook-Signature | sha256= followed by the hex HMAC of the raw body |
X-Webhook-Signature-Version | v1 |
X-Webhook-Timestamp | The event’s ISO timestamp |
X-Webhook-ID | Your 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
2xxstatus within 30 seconds; anything else counts as a failure. - Failures are retried up to
maxRetriestimes, waitingretryBackoffMs × 2^(attempt − 1), at most five minutes. - Retries can deliver the same event twice; use
ride_id,event_typeandtimestampto 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.