DeveloperRegister an app
Menu

Webhooks

Sparro sends a signed HTTPS request to your endpoint when something changes for a company connected to your app. Webhooks are available on Partner and Platform. You create an endpoint and see its signing secret in the developer portal.

Events

shift.created
A shift or hours entry is created through the API (POST /shifts or POST /shifts/hours). Sent to apps with the shifts:read scope for that company.
mileage.created
A mileage entry is created through the API (POST /mileage). Sent to apps with the mileage:read scope for that company.
connection.revoked
The company removes your app. This is the last event you get for that company; delete what you hold on it that you no longer need.
webhook.test
You press Send test event on an endpoint in the developer portal. It carries no company and an empty data object.

New event types and new fields can appear without notice. Ignore the ones you do not know.

Delivery

Each event is a POST with a JSON body. Respond with any 2xx within 10 seconds. Anything else is retried, after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours. After the last retry the delivery is given up; after 5 deliveries in a row have used all their retries, the endpoint is disabled with a reason, and you turn it back on in the portal. The test event is sent once and never retried.

request
POST /hooks/sparro HTTP/1.1
Sparro-Signature: t=<unix seconds>,v1=<hex>
Sparro-Event-Id: <event id>
Sparro-Delivery-Id: <delivery id>
Content-Type: application/json

{ "id": "<event id>", "type": "shift.created", "created": "<ISO 8601>",
  "data": { "org_id": "…", "shift": { "id": "…", "employee_ref": "emp_…", "start_time": "…", "end_time": "…", "hours": 7.5 } } }

Verify the signature

Compute an HMAC-SHA256 of t + "." + raw_body with your endpoint's signing secret and compare it with v1 in constant time. Use the body exactly as it arrived, before any JSON parsing. Reject events whose t is more than 5 minutes from your clock.

pseudocode
t, v1  = parse(request.headers["Sparro-Signature"])
signed = t + "." + request.raw_body
expect = hex(HMAC_SHA256(signing_secret, signed))
valid  = constant_time_equal(expect, v1)
         and abs(now() - t) <= 300

Run this test vector through your verifier before you go live. It uses a fixed fake secret that signs nothing real.

test vector
secret    = "whsec_test_vector_not_a_real_secret"
t         = 1792341127
raw_body  = {"id":"7b5a1f0e-3c2d-4e8a-9b61-0d4f2a8c9e11","type":"webhook.test","data":{}}
signature = t=1792341127,v1=8e09ed0e7905e2cac6d751fb9e1c7f90ccb0b55f58703146387aed666bab5192

Replay and duplicates

Every delivery of one event carries the same event id: in the body as id and in the Sparro-Event-Id header. Retries and replays send it again, so store the ids you have handled and ignore repeats. The Sparro-Delivery-Id header identifies one delivery; a replay from the portal is a new delivery of the same event. Missed events stay in the delivery log for 30 days and can be replayed for the first 7.