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.

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's events list. See Versioning.
  • eventId — a stable identifier for this delivery. It is identical across every retry, 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 the cl-request-id header.
{
  "eventId": "WebhookRequest#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.