Webhooks

Delivery, retries and ordering

When Gauge retries, when it gives up, why you must deduplicate, and why events can arrive out of order.

Success and failure

A delivery succeeds when your endpoint answers any 2xx status within 10 seconds. Everything else is a failure and is retried:

  • any other status, including 3xx: Gauge never follows redirects, because the URL you registered is the one that was checked;
  • no answer within 10 seconds;
  • a connection, DNS or TLS error.

Each endpoint's delivery log in Developers → Webhooks shows every attempt, with the status or the reason it failed ("Timed out after 10 s", "HTTP 503") and an excerpt of your response.

Retries

A failed delivery is retried on this schedule, then marked dead:

AttemptWhen
1Immediately
230 seconds after attempt 1 failed
35 minutes later
41 hour later
56 hours later
Then dead: no more automatic attempts

So an event survives a little over seven hours of downtime on its own. For a longer outage, replay the dead deliveries once you are back.

Endpoints that keep failing are switched off

An endpoint whose deliveries have all failed for 3 days in a row (no success since its first failure) is disabled automatically: it gets enabled: false and disabledReason: "consecutive_failures", and the member who owns it is told in the app and by email. One successful delivery at any point resets the count.

While it is off, nothing is delivered to it. Fix the receiver, then switch it back on in the console or with PATCH /v2/webhook-endpoints/{id} { "enabled": true } (which clears disabledReason), and replay what you missed: events are kept for 30 days.

Headers

Every delivery is a POST with Content-Type: application/json and:

Header
Gauge-Signaturet=<epoch ms>,v1=<hex>. See Verify signatures.
Gauge-Event-IdThe event id, the same as the body's id. Dedupe on it.
Gauge-Event-TypeThe event name, e.g. message.received.
Gauge-Delivery-Attempt1 on the first try, then 2, 3, … across retries and redeliveries.
Gauge-Webhook-IdThe endpoint's id.
x-pulse-signature, x-pulse-timestamp, x-pulse-eventLegacy copies of the signature, t and event name, for receivers written before API v2.

Size

A body is at most 64 KB. A longer message's text is cut, with truncated: true and a fetchUrl to read it in full; see Large messages.

Deduplicate on the event id

The same event can reach you more than once: a retry after your server processed the event but timed out answering, a manual resend, or a replay. Every copy carries the same id.

Record the ids you have processed and skip any you have seen. Keep them for at least a few days, longer than the retry schedule.

-- One way: a unique key, and treat a conflict as "already handled".
CREATE TABLE gauge_events_seen (id text PRIMARY KEY, received_at timestamptz NOT NULL DEFAULT now());
INSERT INTO gauge_events_seen (id) VALUES ($1) ON CONFLICT (id) DO NOTHING RETURNING id;
-- No row returned → a duplicate. Answer 2xx and do nothing.

Ordering

Deliveries are not guaranteed to arrive in the order the events happened. Events are sent in parallel, and a failed delivery is retried later while newer ones go through.

  • Use occurredAt to order events about the same thing, and ignore an update older than what you already have.
  • For anything where the latest state matters, treat the event as a signal and fetch the current state from the API by entityId.

Answer fast

Your endpoint has 10 seconds, and Gauge waits for each answer. Verify the signature, store the event (or put it on a queue), answer 2xx, then do the slow work. An endpoint that does its work inline risks timeouts, and every timeout becomes a retry.

Security

  • Use an https URL: deliveries carry your customers' data. Only addresses on the public internet are accepted; private, loopback and link-local addresses (and cloud metadata endpoints) are refused, both when you save the endpoint and again when Gauge connects.
  • Verify the signature on every delivery.
  • Keep the signing secret on your server, and rotate it if it may have leaked.

On this page