Skip to Content

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

EventWhen it fires
job.createdA new job is created
job.updatedAny field on a job changes
job.deliveredA job reaches the Delivered status
job.cancelledA job is cancelled
step.createdA new step is added to a job
step.updatedAny field on a step changes
step.completedA step is marked completed
bay.status_changedA bay’s status changes (e.g. into or out of maintenance)
tech.clocked_inA technician clocks in
tech.clocked_outA 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

  1. Read the raw request body as a UTF-8 string (do not parse it first)
  2. Compute HMAC-SHA256(signingSecret, rawBody) and hex-encode the result
  3. 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:

AttemptDelay
1 (immediate)
21 minute
35 minutes
430 minutes
52 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

ActionDescription
List webhooksGET /v1/platform/webhooks — see all registered endpoints
Update a webhookPATCH /v1/platform/webhooks/{id} — change the URL, events, or active state
Rotate the secretPATCH /v1/platform/webhooks/{id} with { "rotateSecret": true } — generates a new signing secret
Delete a webhookDELETE /v1/platform/webhooks/{id} — removes the registration and all delivery history