Webhooks
We post every status change to the URL you register with us. This is the primary channel; polling is the fallback for when your endpoint was unreachable.
Envelope
POST https://your-domain.example/hooks/hizliyo
X-Hizliyo-Signature: sha256=4f7c1e9a2b8d0356f1a4c7e93b2d5087...
X-Hizliyo-Event: delivery.status_changed
X-Hizliyo-Attempt: 1
Content-Type: application/json{
"eventId": "01J9XK4M2T7QW3ZR8B6N5FVCDA",
"event": "delivery.status_changed",
"occurredAt": "2026-08-04T19:03:22+03:00",
"data": {
"orderId": "e5a9f0d3-7c21-4b88-a06e-3f9182bd4471",
"externalOrderId": "ADS-ORD-99213",
"status": "PickedUp",
"courier": {
"name": "Emre K.",
"phone": "5307654321",
"vehicleType": "Motorcycle"
},
"location": { "latitude": 39.921004, "longitude": 32.855210 },
"etaDropoffAt": "2026-08-04T19:14:00+03:00"
}
}The data object is byte-for-byte the same shape returned by GET /orders/{orderId} — write one parser, use it in both places.
Courier surnames are abbreviated. We pass on the minimum needed to complete the delivery.
Event types
| Event | When | Payload |
|---|---|---|
delivery.status_changed | Any status transition | Order and courier state |
store.activated | Fleet confirmed the store | Store id and fleet |
store.migrating | Branch is becoming a Hızlıyo partner | Store id and closing date |
store.deactivated | Store closed | Store id and reason: MigratedToHizliyo, FleetSuspended, VendorRequest |
fleet.payment_overdue | The store's fleet has an overdue balance | Store ids at risk |
Ignore what you do not know
New event types may be added without a version bump. Treat an unrecognised X-Hizliyo-Event as a no-op and return 2xx — do not fail the request.
Verifying the signature
Compute an HMAC-SHA256 of the raw request body with your webhook secret and compare it to the header in constant time. Never parse the body before verifying it.
import crypto from 'node:crypto';
function verify(rawBody, headerValue, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
const a = Buffer.from(headerValue ?? '');
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}public static bool Verify(string rawBody, string headerValue, string secret)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
var expected = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(headerValue ?? string.Empty),
Encoding.UTF8.GetBytes(expected));
}Use the raw body
Serialising the parsed JSON back to a string changes whitespace and key order, and the signature will never match. Capture the raw bytes before any middleware touches them.
Responding
Return 2xx as soon as you have stored the event. Do the real work afterwards — a webhook handler that waits on your database or on a third party will time out and cause a retry.
Any non-2xx or a timeout counts as a failure.
Retries
Failed deliveries are retried with exponential backoff for up to 24 hours. X-Hizliyo-Attempt tells you which attempt this is. After 24 hours we stop and flag the endpoint as unreachable in our panel.
If your endpoint was down, catch up by querying the affected orders — see Order status.
This is not the same as order retries
We retry outbound notifications because the package is already on its way and you need to know. We never retry your inbound order request — see Orders.
Duplicates and ordering
Events carry a unique eventId. Under retry conditions the same event may arrive more than once — deduplicate on eventId.
Order is not guaranteed. Use occurredAt, and ignore a status that is behind the one you have already recorded for that order.