RAG White-Label

Webhooks

Every event we send, api_account.index_ready plus the four connection lifecycle events, with payloads, HMAC-SHA256 verification in Ruby and Node, the at-least-once delivery contract, and the SSRF rules on your endpoint.

Raw markdown guide for AI
Guide contents

Everything we send goes to the account's webhook_url, falling back to your organisation default, signed the same way. No URL configured means no webhooks (polling still works).

Event Fires when
api_account.index_ready An account's website finished its first crawl.
connection.authorised A connected source completed its OAuth exchange, or a direct source was created and validated.
connection.auth_failed An exchange failed, or a refresh token died during a scheduled sync.
connection.disconnected You deleted one connected source.
connection_type.revoked A connection type was withdrawn from your organisation, destroying every connection of it across your fleet.

api_account.index_ready

Crawling is deliberately slow (anti-detection), so don't block on it. This event only applies to accounts created with a site_url.

When the first crawl finishes we POST:

Response fields

eventstring
Always api_account.index_ready for this webhook.
api_account_idinteger
Context Link's own id for the account whose index is ready.
uidstring
The customer id you provisioned the account with.
connection_idinteger
The website connection that finished crawling.
postsinteger
Documents indexed by that website connection.
chunksinteger
Embedded passages indexed by that website connection.
synced_attimestamp
When the crawl finished, as a UTC ISO 8601 string.

The connection lifecycle events

The three connection.* events share one payload shape and go to the webhook URL of the customer the source belongs to. They are how you keep your own "connected sources" screen honest without polling.

Field Type Description
event string connection.authorised, connection.auth_failed or connection.disconnected.
api_account_uid string The customer id you provisioned the account with.
connection_id integer Context Link's id for the connection.
connection_type string The type, in the same form you sent it (google_doc, youtube).
auth_status string The connection's authorisation state at the moment the event fired.
reason string Only on failures: why, truncated to 200 characters. Present as a support message, not as something to branch on.

connection.auth_failed is the one that matters operationally. Refresh tokens die silently, and across a fleet of thousands that is invisible rot: we can see it, but you are the only one who can ask your customer to reconnect. Route it somewhere a human or an email sequence will act on, then send them through re-authorisation.

connection.disconnected fires as the row is destroyed, so by the time you receive it the source and its content are already gone. It carries the same shape with no reason.

connection_type.revoked

A connection type can be withdrawn from your organisation, either by an admin of yours on the Connection types screen or by us. When that happens every connection of that type across your entire fleet is destroyed. You get one org-level event, not one per customer.

This goes to your organisation default webhook URL, because it is not about one customer. Fanning it out as 1,204 signed deliveries in a burst would read as an outage at your end; instead, reconcile per customer at your own pace with GET .../connections.

Signatures and delivery

Every delivery carries these headers, shown in the examples rail:

Verifying the signature

The signature is HMAC-SHA256(signing_secret, "<timestamp>.<raw body>"), hex-encoded. Verify it against the raw request body before parsing, compare with a constant-time function, and reject a timestamp skewed more than 5 minutes from your clock.

A Ruby implementation of that check:

The same check in Node, using Express with the raw body preserved:

Delivery contract

Delivery is at least once for every event: under normal operation you get exactly one call, but a queue or crash at the wrong moment can repeat it. Key on (uid, event) for api_account.index_ready, and on (connection_id, event) for the connection events, and make your handler idempotent.

Return any 2xx directly to acknowledge. Anything else is a failure and is retried about five times with exponential backoff, including a redirect: 3xx is treated as a failure, not followed, so point the webhook at its final URL.

SSRF constraints on the webhook URL

Your endpoint must be a publicly routable http(s) URL. These are refused outright (logged and never retried) rather than delivered:

  • any scheme other than http or https;
  • a URL with embedded credentials (https://user:pass@…);
  • a host resolving to loopback, private (RFC 1918), CGNAT, link-local or multicast space, IPv4 or IPv6.

The same check runs when you set the URL, so a bad target is a 422 at configuration time instead of a silent dead letter later.