Skip to content

Callbacks

A callback is an HTTPS request from the Gregor server to your service, sent when someone acknowledges an alert. Use it to close an incident, stop an escalation, or record who took the alert, without polling receipts.

The person acknowledging decides whether the callback is sent. It is never sent from their device.

Callbacks are configured per channel by its owner or an administrator.

  1. On the Channels page, open the channel’s Send URLs.
  2. Under Acknowledgement callback, choose Configure callback.
  3. Enter the Callback URL. It must be a publicly reachable https URL. Fixed query parameters are kept.
  4. Save. The section shows Callback enabled and a Callback signing key.
  5. Choose Reveal key, copy the key, and store it in your service’s secret storage. Do not put it in scripts, logs, or clients.

Add "callback": true to the alert. The alert must be one that can be acknowledged, so it is either urgent, sets require_ack, or sets receipt; a callback on any other alert makes it require acknowledgement.

Terminal window
curl -X POST "$SEND_URL" \
-H 'Content-Type: application/json' \
-d '{"alert": {"title": "Database unavailable"}, "level": "urgent", "callback": true}'

The request is rejected with callback_not_configured if the channel has no enabled callback, so a callback that could never fire is not accepted.

When someone acknowledges the alert, the app asks: Acknowledge only, or Acknowledge and send Callback. Only the first acknowledgement counts; acknowledging again on another device changes nothing.

POST /talaria/ack HTTP/1.1
Content-Type: application/json
User-Agent: Receipt-Callback/1
X-Webhook-Event-Id: cb1…
X-Webhook-Key-Id: ck1…
X-Webhook-Timestamp: 1792311330
X-Webhook-Attempt: 1
X-Webhook-Signature: v1=5d41402abc4b2a76b9719d911017c592…
{
"event_id": "cb1…",
"type": "receipt.acknowledged",
"occurred_at": "2026-10-27T08:15:30Z",
"channel_id": "ch1…",
"receipt_id": "rcp_9a02…",
"message_id": "msg_4f1c…",
"status": "acked",
"acknowledged": true,
"acknowledged_at": "2026-10-27T08:15:30Z",
"acknowledged_by": { "user_id": "…", "device_address": "dv1…" }
}

The body never contains the alert’s title, body, or payload. Match it to your own records with message_id or receipt_id.

Before trusting a callback:

  1. Look up the key for X-Webhook-Key-Id.
  2. Reject it if X-Webhook-Timestamp is more than five minutes from your clock.
  3. Compute HMAC-SHA256 over timestamp + "." + raw body, using the key exactly as copied from the app, and compare it in constant time with the hex value after v1=.
  4. Ignore an X-Webhook-Event-Id you have already processed.
  5. Respond with any 2xx status only after you have stored the event.
import hashlib, hmac, time
def verify(headers, raw_body: bytes, key: str) -> bool:
timestamp = headers["X-Webhook-Timestamp"]
if abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(key.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
received = headers["X-Webhook-Signature"].removeprefix("v1=")
return hmac.compare_digest(expected, received)

Compute the signature over the bytes you received, before parsing the JSON.

Delivery is at least once, so the same event can arrive more than once; deduplicate by event ID.

  • Network errors, timeouts, 408, 429, and 5xx responses are retried, starting after one minute and backing off up to one hour between attempts. A Retry-After header is respected.
  • Other 4xx responses stop the retries, because they usually mean the endpoint is misconfigured.
  • Retries stop 24 hours after the acknowledgement.

The receipt shows the callback’s status, attempt_count, and delivered_at.

  • Rotate key creates a new signing key. New callbacks use it immediately; callbacks already queued keep the key they were created with, and the previous key keeps working for 24 hours. Deploy the new key to your service, keyed by X-Webhook-Key-Id, within that time.
  • Disable callback stops new alerts from requesting callbacks. Callbacks already queued are still delivered.