Webhooks
Webhooks deliver real-time notifications to your server whenever something changes — a job or step (Scheduler) or money movement (Payments). Instead of polling the API, you register an endpoint URL and BayWise pushes events to it. Pick the events you care about across both products when you register.
Register an endpoint
POST /v1/platform/webhooks
{
"url": "https://yourapp.example.com/hooks/baywise",
"events": ["job.created", "job.delivered", "step.completed"]
}The response includes a signingSecret — store it securely. BayWise uses it to sign every delivery so you can verify requests came from us.
{
"id": "wh_...",
"url": "https://yourapp.example.com/hooks/baywise",
"events": ["job.created", "job.delivered", "step.completed"],
"signingSecret": "a3f9...",
"isActive": true
}Available events
Scheduler
| Event | When it fires |
|---|---|
job.created | A new job is created |
job.updated | Any field on a job changes |
job.delivered | A job reaches the Delivered status |
job.cancelled | A job is cancelled |
step.created | A new step is added to a job |
step.updated | Any field on a step changes |
step.completed | A step is marked completed |
bay.status_changed | A bay’s status changes (e.g. into or out of maintenance) |
tech.clocked_in | A technician clocks in |
tech.clocked_out | A technician clocks out |
The exact, current set of subscribable events is always shown in Settings → Developer when you register or edit a webhook — register only the ones you need.
Payload shape
Every webhook delivery sends a POST to your endpoint with a JSON body:
{
"event": "job.delivered",
"occurredAt": "2026-06-15T09:42:00Z",
"data": {
"id": "...",
"status": "delivered",
"job_number": "RO-2026-0143",
"vehicle_plate": "ABC 123",
"arrival_time": "2026-06-15T08:12:00Z",
"promise_time": "2026-06-15T17:00:00Z",
"insurance_claim": null
}
}⚠️ data uses the database’s snake_case field names, not the REST API’s
camelCase. The payload is the changed row exactly as stored — so it is
vehicle_plate here and vehiclePlate over REST, the same fact under two
spellings. Map it explicitly rather than reusing your REST response types.
data carries every column on the row, so job_number (the RO’s number)
and arrival_time (when the vehicle physically arrived) are delivered
automatically — no change to your subscription is needed to start receiving
them. arrival_time is null on an RO created before arrival was recorded,
and on a pre-booked job whose car has not come in yet.
Verifying signatures
BayWise signs every delivery with HMAC-SHA256 using your signingSecret. Verify the signature to confirm the delivery is authentic.
The signature is in the X-BayWise-Signature request header:
X-BayWise-Signature: sha256=a1b2c3d4...Verification steps
- Read the raw request body as a UTF-8 string (do not parse it first)
- Compute
HMAC-SHA256(signingSecret, rawBody)and hex-encode the result - Compare it to the value after
sha256=in the header
import hmac, hashlib
def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
received = signature_header.removeprefix("sha256=")
return hmac.compare_digest(expected, received)import { createHmac, timingSafeEqual } from 'node:crypto';
function verifySignature(secret, rawBody, signatureHeader) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const received = signatureHeader.replace('sha256=', '');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(received, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}Always use a constant-time comparison (
hmac.compare_digest/timingSafeEqual) to prevent timing attacks.
Retry behaviour
If your endpoint returns a non-2xx response or times out (10 seconds), BayWise retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 (immediate) | — |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the delivery is marked exhausted and no further retries occur. You can monitor delivery health in Settings → Integrations → Webhooks.
Respond quickly
Your endpoint should return 200 within 10 seconds. If processing takes longer, accept the webhook immediately and handle it asynchronously.
Managing registrations
| Action | Description |
|---|---|
| List webhooks | GET /v1/platform/webhooks — see all registered endpoints |
| Update a webhook | PATCH /v1/platform/webhooks/{id} — change the URL, events, or active state |
| Rotate the secret | PATCH /v1/platform/webhooks/{id} with { "rotateSecret": true } — generates a new signing secret |
| Delete a webhook | DELETE /v1/platform/webhooks/{id} — removes the registration and all delivery history |