Webhooks
Webhooks let ClientLoop notify your systems when something happens — for example, when an application's status changes — by sending an HTTP request to a URL you control, instead of you having to poll the API.
A webhook is a subscription owned by an agency or an org. It has:
- a URL — the HTTPS endpoint ClientLoop delivers to;
- a list of events it is subscribed to (see Events);
- an enabled flag controlling whether deliveries are sent;
- a signing secret, returned once when the webhook is created, that you use to verify a delivery's signature and confirm it genuinely came from ClientLoop.
Delivery
When a subscribed event occurs, ClientLoop sends an HTTP POST to the
webhook's URL with the event payload as a JSON body. Each request is signed —
verify the signature before trusting it — and you
respond with 200 or 201 to acknowledge the delivery.
If your endpoint returns any other status (or is unreachable), the delivery is
retried with exponential backoff — growing up to one hour between
attempts — for up to 7 days. After that the delivery is marked failed and
no further attempts are made.
Every delivery is recorded, retained for 90 days, and can be retrieved for a webhook through the API so you can inspect attempts, response codes, and the delivered payload.
Resending a delivery
A delivered or failed delivery can be resent from the webhook's page in the
portal, or with the webhookRequestReplay mutation. The delivery is put back in
the queue and sent again unchanged — the same payload, URL and eventId — and is
retried as usual if your endpoint does not accept it. A delivery that is still
pending is already queued and can't be resent, and a suspended webhook can't
be resent to.
Because the eventId is unchanged, a handler that deduplicates on it treats a
resent delivery as an event it has already seen. To have it processed again, clear
that event from your deduplication store first.
Payload format
Every payload is a JSON object. Alongside the event-specific fields it always carries three common fields:
type— identifies the event and its payload version (e.g.application.status.changed.v1), so a single endpoint can dispatch on it. It matches the entry in the webhook'seventslist. See Versioning.eventId— a stable identifier for the event. It is identical across every retry and every resend, so use it to deduplicate (deliveries are retried for up to 7 days, so the same event can reach you more than once).requestId— a fresh identifier minted for each individual delivery attempt, useful for tracing a specific request in your logs. It is also sent as thecl-request-idheader.
{
"eventId": "2cVQF8s0u9c2bq5b3pTn1mY7kde",
"requestId": "2dWRg9t1v0d3cr6c4qUo2nZ8lef",
"type": "application.status.changed.v1",
"...": "event-specific fields"
}See Events for the catalog of events and their payloads.
Versioning
Each payload type is versioned with a trailing .v<n> (e.g.
application.status.changed.v1). Within a version, changes are only ever
additive: new fields may be added to an existing version, but a field is
never renamed or removed without publishing a new payload version (e.g.
application.status.changed.v2). Parse defensively and ignore unknown fields so
an added field does not break your handler.
You subscribe to a specific version by listing its type in the webhook's
events, so moving to a new version is always an explicit choice on your part.
When a new version is published, the previous one is deprecated rather than removed immediately:
- These docs are updated as versions are deprecated and new versions are added.
- An email notice is sent to the technical contact listed on the organization.
- A deprecated version generally remains available for 180 days after its deprecation, giving you time to migrate.