Get notified when a menu changes instead of polling it. Events: product.created, product.updated, product.deleted, category.created, category.updated, category.deleted, restaurant.updated, restaurant.deleted. Each is a POST with a JSON body { id, type, api_version: "2026-10-01", created_at, restaurant: { id, identifier }, data: { object, changes? } }: object is the product, category or restaurant as the API returns it (categories add product_ids and restaurants add category_ids, in display order; a deleted item carries its last state), and changes lists the top-level fields an update touched. Writes that change nothing visible send nothing. Verify every request: the Delimenu-Signature header is t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw body>" with the endpoint secret; compare in constant time and reject a t older than 5 minutes. Answer any 2xx within 10 seconds and do the work afterwards. Anything else is retried with exponential backoff, up to 8 attempts over about a day, so deduplicate by id (also in the Delimenu-Event-Id header). An endpoint that exhausts every retry on 20 events in a row is switched off. Only restaurants with an active subscription or trial send events.
Every operation comes with its curl and an example response; the keys and ids in the examples are made up. Fields marked with * are required.
List the webhooks of a restaurant get /restaurants/{ref}/webhooksrequires menu:read listWebhooks
Signing secrets are never returned here; only when an endpoint is created or its secret rotated.
Parameters Name In Description ref* path The restaurant identifier (the slug in its public URL, e.g. "pizzeria-roma") or its id, as returned by GET /restaurants
Example curl -X GET "https://delimenu.co/api/v1/restaurants/pizzeria-roma/webhooks" \
-H "Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Response 200 {
"webhooks" : [
{
"id" : "w7Kd2PqR9sLm4XvB1nTc" ,
"url" : "https://pos.example.com/delimenu/webhooks" ,
"events" : [
"product.created" ,
"product.updated" ,
"product.deleted"
],
"enabled" : true ,
"disabled_reason" : null ,
"created_at" : "2026-10-01T15:04:05.000Z" ,
"last_delivery_at" : "2026-10-02T09:30:00.000Z" ,
"last_status" : 200
}
]
} Response fields Field Type Description webhooks* object[]id* stringurl* stringevents* "product.created" | "product.updated" | "product.deleted" | "category.created" | "category.updated" | "category.deleted" | "restaurant.updated" | "restaurant.deleted"[]enabled* booleandisabled_reason* "failing" | "manual""failing" when it was switched off for failing too many events in a row created_at* string | nulllast_delivery_at* string | nulllast_status* integer | nullHTTP status of the last attempt; null if it never answeredmin -9007199254740991 · max 9007199254740991
Errors 401 UNAUTHORIZED — the key is missing, unknown or revoked402 NOT_PREMIUM — the restaurant has no active subscription or trial403 FORBIDDEN — the key lacks the scope this endpoint needs404 NOT_FOUND — no such restaurant in this account, or no such item in it500 INTERNAL — something failed on our side; retry in a momentRegister a webhook post /restaurants/{ref}/webhooksrequires menu:write createWebhook
Starts sending the chosen events to the URL. The response carries the signing secret, the only time it is shown: store it and use it to verify Delimenu-Signature. Up to 5 per restaurant; one URL once per restaurant.
Parameters Name In Description ref* path The restaurant identifier (the slug in its public URL, e.g. "pizzeria-roma") or its id, as returned by GET /restaurants
Body (JSON) Field Type Description url* stringWhere the events are POSTed: https, a public domain, port 443up to 2048 characters events* "product.created" | "product.updated" | "product.deleted" | "category.created" | "category.updated" | "category.deleted" | "restaurant.updated" | "restaurant.deleted"[]The events this endpoint receives
Example curl -X POST "https://delimenu.co/api/v1/restaurants/pizzeria-roma/webhooks" \
-H "Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://pos.example.com/delimenu/webhooks","events":["product.created","product.updated","product.deleted"]}' Response 201 {
"webhook" : {
"id" : "w7Kd2PqR9sLm4XvB1nTc" ,
"url" : "https://pos.example.com/delimenu/webhooks" ,
"events" : [
"product.created" ,
"product.updated" ,
"product.deleted"
],
"enabled" : true ,
"disabled_reason" : null ,
"created_at" : "2026-10-01T15:04:05.000Z" ,
"last_delivery_at" : null ,
"last_status" : null
},
"secret" : "whsec_Zx8Qm2Lk5Vb9Tn3Rw7Yc1Hd4Fg6Js0Pa"
} Response fields Field Type Description webhook* objectid* stringurl* stringevents* "product.created" | "product.updated" | "product.deleted" | "category.created" | "category.updated" | "category.deleted" | "restaurant.updated" | "restaurant.deleted"[]enabled* booleandisabled_reason* "failing" | "manual""failing" when it was switched off for failing too many events in a row created_at* string | nulllast_delivery_at* string | nulllast_status* integer | nullHTTP status of the last attempt; null if it never answeredmin -9007199254740991 · max 9007199254740991 secret* stringThe signing secret. Shown only in this response; store it now
Errors 400 VALIDATION — a field is missing or malformed; `issues` names it401 UNAUTHORIZED — the key is missing, unknown or revoked402 NOT_PREMIUM — the restaurant has no active subscription or trial403 FORBIDDEN — the key lacks the scope this endpoint needs404 NOT_FOUND — no such restaurant in this account, or no such item in it409 CONFLICT — this restaurant already has a webhook with that URL500 INTERNAL — something failed on our side; retry in a momentUpdate a webhook patch /restaurants/{ref}/webhooks/{id}requires menu:write updateWebhook
Changes only the fields given. enabled: true turns back on an endpoint that was switched off for failing, and clears its failure count.
Parameters Name In Description ref* path The restaurant identifier (the slug in its public URL, e.g. "pizzeria-roma") or its id, as returned by GET /restaurants id* path The webhook id, from GET /restaurants/{ref}/webhooks
Body (JSON) Field Type Description urlstringWhere the events are POSTed: https, a public domain, port 443up to 2048 characters events"product.created" | "product.updated" | "product.deleted" | "category.created" | "category.updated" | "category.deleted" | "restaurant.updated" | "restaurant.deleted"[]The events this endpoint receives enabledbooleanfalse stops deliveries; true turns a switched-off endpoint back on and clears its failure count
Example curl -X PATCH "https://delimenu.co/api/v1/restaurants/pizzeria-roma/webhooks/w7Kd2PqR9sLm4XvB1nTc" \
-H "Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"enabled":true}' Response 200 {
"webhook" : {
"id" : "w7Kd2PqR9sLm4XvB1nTc" ,
"url" : "https://pos.example.com/delimenu/webhooks" ,
"events" : [
"product.created" ,
"product.updated" ,
"product.deleted"
],
"enabled" : true ,
"disabled_reason" : null ,
"created_at" : "2026-10-01T15:04:05.000Z" ,
"last_delivery_at" : "2026-10-02T09:30:00.000Z" ,
"last_status" : 200
}
} Response fields Field Type Description webhook* objectid* stringurl* stringevents* "product.created" | "product.updated" | "product.deleted" | "category.created" | "category.updated" | "category.deleted" | "restaurant.updated" | "restaurant.deleted"[]enabled* booleandisabled_reason* "failing" | "manual""failing" when it was switched off for failing too many events in a row created_at* string | nulllast_delivery_at* string | nulllast_status* integer | nullHTTP status of the last attempt; null if it never answeredmin -9007199254740991 · max 9007199254740991
Errors 400 VALIDATION — a field is missing or malformed; `issues` names it401 UNAUTHORIZED — the key is missing, unknown or revoked402 NOT_PREMIUM — the restaurant has no active subscription or trial403 FORBIDDEN — the key lacks the scope this endpoint needs404 NOT_FOUND — no such restaurant in this account, or no such item in it409 CONFLICT — this restaurant already has a webhook with that URL500 INTERNAL — something failed on our side; retry in a momentDelete a webhook delete /restaurants/{ref}/webhooks/{id}requires menu:write deleteWebhook
Stops every delivery at once, retries already queued included.
Parameters Name In Description ref* path The restaurant identifier (the slug in its public URL, e.g. "pizzeria-roma") or its id, as returned by GET /restaurants id* path The webhook id, from GET /restaurants/{ref}/webhooks
Example curl -X DELETE "https://delimenu.co/api/v1/restaurants/pizzeria-roma/webhooks/w7Kd2PqR9sLm4XvB1nTc" \
-H "Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Response 200 {
"deleted" : "w7Kd2PqR9sLm4XvB1nTc"
} Response fields Field Type Description deleted* string
Errors 401 UNAUTHORIZED — the key is missing, unknown or revoked402 NOT_PREMIUM — the restaurant has no active subscription or trial403 FORBIDDEN — the key lacks the scope this endpoint needs404 NOT_FOUND — no such restaurant in this account, or no such item in it500 INTERNAL — something failed on our side; retry in a momentRotate the signing secret post /restaurants/{ref}/webhooks/{id}/secretrequires menu:write rotateWebhookSecret
Replaces the secret; the old one stops verifying on the very next delivery. The response is the only time the new one is shown.
Parameters Name In Description ref* path The restaurant identifier (the slug in its public URL, e.g. "pizzeria-roma") or its id, as returned by GET /restaurants id* path The webhook id, from GET /restaurants/{ref}/webhooks
Example curl -X POST "https://delimenu.co/api/v1/restaurants/pizzeria-roma/webhooks/w7Kd2PqR9sLm4XvB1nTc/secret" \
-H "Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Response 200 {
"webhook" : {
"id" : "w7Kd2PqR9sLm4XvB1nTc" ,
"url" : "https://pos.example.com/delimenu/webhooks" ,
"events" : [
"product.created" ,
"product.updated" ,
"product.deleted"
],
"enabled" : true ,
"disabled_reason" : null ,
"created_at" : "2026-10-01T15:04:05.000Z" ,
"last_delivery_at" : "2026-10-02T09:30:00.000Z" ,
"last_status" : 200
},
"secret" : "whsec_Zx8Qm2Lk5Vb9Tn3Rw7Yc1Hd4Fg6Js0Pa"
} Response fields Field Type Description webhook* objectid* stringurl* stringevents* "product.created" | "product.updated" | "product.deleted" | "category.created" | "category.updated" | "category.deleted" | "restaurant.updated" | "restaurant.deleted"[]enabled* booleandisabled_reason* "failing" | "manual""failing" when it was switched off for failing too many events in a row created_at* string | nulllast_delivery_at* string | nulllast_status* integer | nullHTTP status of the last attempt; null if it never answeredmin -9007199254740991 · max 9007199254740991 secret* stringThe signing secret. Shown only in this response; store it now
Errors 401 UNAUTHORIZED — the key is missing, unknown or revoked402 NOT_PREMIUM — the restaurant has no active subscription or trial403 FORBIDDEN — the key lacks the scope this endpoint needs404 NOT_FOUND — no such restaurant in this account, or no such item in it500 INTERNAL — something failed on our side; retry in a momentSend a test event post /restaurants/{ref}/webhooks/{id}/testrequires menu:write testWebhook
POSTs a signed "ping" event now and returns how the endpoint answered. A failing endpoint is still a 200 here, with ok: false. Works on a switched-off endpoint too.
Parameters Name In Description ref* path The restaurant identifier (the slug in its public URL, e.g. "pizzeria-roma") or its id, as returned by GET /restaurants id* path The webhook id, from GET /restaurants/{ref}/webhooks
Example curl -X POST "https://delimenu.co/api/v1/restaurants/pizzeria-roma/webhooks/w7Kd2PqR9sLm4XvB1nTc/test" \
-H "Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Response 200 {
"delivery" : {
"id" : "d4Nf8Kq2Rm6Xs1Vb9Lt3" ,
"event_id" : "evt_test_8c1e4a7f2b9d3e6a0c5f" ,
"type" : "ping" ,
"attempt" : 1 ,
"ok" : true ,
"http_status" : 200 ,
"duration_ms" : 184 ,
"error" : null ,
"response_excerpt" : "{ \" received \" :true}" ,
"created_at" : "2026-10-02T09:30:00.000Z"
}
} Response fields Field Type Description delivery* objectid* stringevent_id* stringtype* stringattempt* integer1 for the first try, then each retrymin -9007199254740991 · max 9007199254740991 ok* booleanhttp_status* integer | nullmin -9007199254740991 · max 9007199254740991 duration_ms* integermin -9007199254740991 · max 9007199254740991 error* string | null"timeout", a network error, or null when the endpoint answered response_excerpt* string | nullcreated_at* string | null
Errors 401 UNAUTHORIZED — the key is missing, unknown or revoked402 NOT_PREMIUM — the restaurant has no active subscription or trial403 FORBIDDEN — the key lacks the scope this endpoint needs404 NOT_FOUND — no such restaurant in this account, or no such item in it500 INTERNAL — something failed on our side; retry in a momentLatest delivery attempts get /restaurants/{ref}/webhooks/{id}/deliveriesrequires menu:read listWebhookDeliveries
The most recent attempts to this endpoint, newest first, retries included. Kept for 14 days.
Parameters Name In Description ref* path The restaurant identifier (the slug in its public URL, e.g. "pizzeria-roma") or its id, as returned by GET /restaurants id* path The webhook id, from GET /restaurants/{ref}/webhooks
Example curl -X GET "https://delimenu.co/api/v1/restaurants/pizzeria-roma/webhooks/w7Kd2PqR9sLm4XvB1nTc/deliveries" \
-H "Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Response 200 {
"deliveries" : [
{
"id" : "d4Nf8Kq2Rm6Xs1Vb9Lt3" ,
"event_id" : "evt_3f9a1c7e5b2d8a4c6e0f1b3d" ,
"type" : "product.updated" ,
"attempt" : 1 ,
"ok" : true ,
"http_status" : 200 ,
"duration_ms" : 184 ,
"error" : null ,
"response_excerpt" : "{ \" received \" :true}" ,
"created_at" : "2026-10-02T09:30:00.000Z"
}
]
} Response fields Field Type Description deliveries* object[]id* stringevent_id* stringtype* stringattempt* integer1 for the first try, then each retrymin -9007199254740991 · max 9007199254740991 ok* booleanhttp_status* integer | nullmin -9007199254740991 · max 9007199254740991 duration_ms* integermin -9007199254740991 · max 9007199254740991 error* string | null"timeout", a network error, or null when the endpoint answered response_excerpt* string | nullcreated_at* string | null
Errors 401 UNAUTHORIZED — the key is missing, unknown or revoked402 NOT_PREMIUM — the restaurant has no active subscription or trial403 FORBIDDEN — the key lacks the scope this endpoint needs404 NOT_FOUND — no such restaurant in this account, or no such item in it500 INTERNAL — something failed on our side; retry in a momentRelated articles