API reference
Webhook events
Every event is a small JSON object with IDs only, signed with your subscription's secret. Generated from the service's OpenAPI webhooks section.
Events
activity.created
An athlete who granted activities:read added an activity. Sent to each active subscription for the app. Respond with any 2xx within 10 seconds. Anything else is tried again, up to 8 attempts in all over about two days. Payloads carry IDs only; fetch records with your token.
{
"id": "evt_0123456789abcdef0123456789abcdef",
"type": "activity.created",
"created_at": "2026-10-09T12:00:00+00:00",
"app_id": "app_0123456789abcdef0123456789abcdef",
"subject": "ccu_0123456789abcdef0123456789abcdef",
"data": {
"activity_id": "act_0123456789abcdef0123456789abcdef"
}
}grant.revoked
An athlete's access for this app ended. Sent to each active subscription for the app. Respond with any 2xx within 10 seconds. Anything else is tried again, up to 8 attempts in all over about two days. Payloads carry IDs only; fetch records with your token.
{
"id": "evt_0123456789abcdef0123456789abcdef",
"type": "grant.revoked",
"created_at": "2026-10-09T12:00:00+00:00",
"app_id": "app_0123456789abcdef0123456789abcdef",
"subject": "ccu_0123456789abcdef0123456789abcdef",
"data": {}
}webhook.test
You asked for a test delivery to one subscription. Sent only to the subscription you tested, after POST /v1/. subject is null. Retried like any other event.
{
"id": "evt_0123456789abcdef0123456789abcdef",
"type": "webhook.test",
"created_at": "2026-10-09T12:00:00+00:00",
"app_id": "app_0123456789abcdef0123456789abcdef",
"subject": null,
"data": {}
}Headers on every delivery
| Header | Value |
|---|---|
CorrConnect-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256 of '<t>.' + raw body, keyed with the subscription secret>. |
CorrConnect-Event-Id | Same on every retry of one event; use it to drop duplicates. |
CorrConnect-Event-Type | activity.created, grant.revoked or webhook.test, the same as type in the body. |
CorrConnect-Delivery-Id | One delivery of the event to one subscription (whd_...); the delivery log uses the same ID. |
CorrConnect-Delivery-Attempt | 1 on the first try, up to 8. |
User-Agent | CorrConnect-Webhooks/ |
Content-Type | application/json |
Event fields
| Field | Type | Notes |
|---|---|---|
id required | string | |
type required | string | One of activity.created, grant.revoked, webhook.test |
created_at required | string (date-time) | |
app_id required | string | |
subject required | string or null | Pairwise subject, the same value GET /v1/profile returns. Null for webhook.test. |
data required | object | |
data.activity_id | string |
Signing
CorrConnect-Signature: t=<unix seconds>, where <hex> is the lowercase hex HMAC-SHA256 of <t>, a full stop and the raw request body, keyed with the subscription secret (whsec_...). Each attempt is signed again with a fresh t. Reject a t more than 300 seconds from your clock. Verification code for JavaScript, Python and Swift is in receive webhooks.
Delivery rules
| Rule | Value |
|---|---|
| Answer with | Any 2xx within 10 seconds for the whole request. Redirects are not followed. |
| Retries | After 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours. |
| Attempts | 8 in total, then the delivery is marked failed. |
| Pause | After 20 failed attempts in a row on one subscription. Turn it back on with the enable endpoint. |
| Order | Not guaranteed. Use created_at in the body. |
| Duplicates | Possible. CorrConnect-Event-Id is the same on every attempt. |
| Delivery log | Kept 30 days after a delivery finishes. |
| Endpoints | Public HTTPS on port 443; at most 3 subscriptions per app. |
| Cost | Free. Webhooks don't count against your rate limit; fetching the record afterwards does. |
Who gets what
activity.created: every active subscription that listens for it, on every app holdingactivities:readin an active grant for the athlete. Only athletes with a verified email trigger it. One file with three sessions sends three events.grant.revoked: the subscriptions of the one app whose grant ended. Pendingactivity.createddeliveries for that athlete and app are dropped at the same time.webhook.test: only the subscription you tested.
Events that happen while a subscription is paused are not stored for later.