Webhooks
Push event updates to your servers in real time. Each delivery is a POST carrying the full event payload + a CMC-Signature header. Verify the signature using the endpoint's signing secret before trusting the body.
◆ Event types
| Type | Triggers when |
|---|---|
| events.created | Editorial team adds a new event to the catalog. |
| events.updated | Material change to an existing event (date, impact score, description, source). |
| events.cancelled | Event is removed from the catalog (cancelled, retracted, or merged into another). |
| events.high-impact | An event reaches impact >= 7.5 for the first time. Fires once per event lifecycle. |
◆ Delivery payload
Every delivery is a JSON body with the same outer shape regardless of type. The data field carries the full event resource (same shape as GET /v2/events/{id}).
{ "id": "evt_dlv_01HW8K2D5N", "type": "events.created", "apiVersion": "2025-10-01", "deliveredAt": "2026-04-29T14:22:08Z", "data": { "id": "evt_2026_05_06_eth_pectra", "slug": "ethereum-pectra-upgrade", "title": "Ethereum Pectra Upgrade", "date": "2026-05-06T12:00:00Z", "isEstimated": false, "displayedDate": "06 May 2026", "coins": [ { "slug": "ethereum", "symbol": "ETH", "name": "Ethereum" } ], "impact": 8.5 } }
◆ Verify the signature
Each POST carries a CMC-Signature header containing the HMAC-SHA256 of the raw request body, signed with your endpoint's signing secret. Reject any request whose signature doesn't match. Anything else lets forged deliveries through.
import crypto from "node:crypto"; import express from "express"; const app = express(); const SECRET = process.env.CMC_WEBHOOK_SECRET; app.post( "/cmc/webhooks", express.raw({ type: "application/json" }), (req, res) => { const sig = req.header("CMC-Signature"); const expected = crypto .createHmac("sha256", SECRET) .update(req.body) .digest("hex"); if ( !sig || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) ) { return res.status(401).end(); } const event = JSON.parse(req.body.toString("utf8")); // ... handle event res.status(200).end(); }, );
◆ Retry policy
- Success window: your endpoint must respond
2xxwithin 10 s. Anything else (4xx, 5xx, timeout, connection error) is treated as a failed delivery. - Backoff: failed deliveries are retried with exponential backoff for up to 24 hours. Schedule: 1m → 5m → 30m → 2h → 6h.
- Endpoint health: if 3+ consecutive deliveries fail in a 24h window, the endpoint enters
degradedstate and surfaces in your dashboard. After 24h of continuous failure, it auto-pauses; resume manually once your receiver is fixed. - Idempotency: every delivery carries a unique
id(e.g.evt_dlv_01HW…). Dedupe on it; retries reuse the same id, so the same delivery never reaches your handler twice.
◆ Endpoints
- GET
/v2/webhooksList your webhooks - POST
/v2/webhooksCreate a webhook - DELETE
/v2/webhooks/{id}Delete a webhook
/v2/webhooksElite+List your webhooks
Returns every webhook you've registered. The signingSecret field is masked; use the rotate endpoint or the dashboard to retrieve a usable secret.
◆ Parameters
No parameters.
◆ Response
| Field | Type | Description |
|---|---|---|
| data | array<Webhook> | All registered webhooks. |
◆ Webhook
| Field | Type | Description |
|---|---|---|
| id | string | Stable webhook id (e.g. `wh_01HW...`). |
| name | string | Friendly label for your dashboard. |
| url | string | Your endpoint that receives `POST` deliveries. |
| events | string[] | Subscribed event types. See the supported list in the resource description. |
| apiVersion | string | API version the payload is shaped against (e.g. `2025-10-01`). Locked at creation time so future API changes don't break your verifier. |
| signingSecret | string | HMAC secret used to sign each delivery's body. Returned in full ONLY on creation and on rotation. Subsequent reads return a masked value. |
| status | string | One of `healthy` · `degraded` · `paused`. `degraded` fires when 3+ consecutive deliveries fail in a 24h window. |
| createdAt | iso8601 | When the webhook was created. |
◆ Request
curl -X GET "https://api.coinmarketcal.com/v2/webhooks" \ -H "Authorization: Bearer $COINMARKETCAL_API_KEY" \ -H "Accept: application/json"
◆ Example response
{ "data": [ { "id": "wh_01HW8K2D5N", "name": "events-prod", "url": "https://api.acme.io/cmc/webhooks", "events": [ "events.created", "events.updated" ], "apiVersion": "2025-10-01", "signingSecret": "whsec_••••5e6f", "status": "healthy", "createdAt": "2026-04-12T08:30:00Z" } ] }
/v2/webhooksElite+Create a webhook
Registers a new endpoint. Returns the full signingSecret; store it now, subsequent reads will mask it.
◆ Parameters
| Field | In | Type | Required | Description |
|---|---|---|---|---|
| name | body | string | yes | Friendly label (≤ 64 chars). |
| url | body | string | yes | Your `https://` endpoint. Must respond `2xx` within 10 s. |
| events | body | string[] | yes | At least one of: `events.created`, `events.updated`, `events.cancelled`, `events.high-impact`. |
| apiVersion | body | string | no | Pin to a specific API version (e.g. `2025-10-01`). Defaults to the latest at creation time. |
◆ Response
| Field | Type | Description |
|---|---|---|
| id | string | Stable webhook id (e.g. `wh_01HW...`). |
| name | string | Friendly label for your dashboard. |
| url | string | Your endpoint that receives `POST` deliveries. |
| events | string[] | Subscribed event types. See the supported list in the resource description. |
| apiVersion | string | API version the payload is shaped against (e.g. `2025-10-01`). Locked at creation time so future API changes don't break your verifier. |
| signingSecret | string | HMAC secret used to sign each delivery's body. Returned in full ONLY on creation and on rotation. Subsequent reads return a masked value. |
| status | string | One of `healthy` · `degraded` · `paused`. `degraded` fires when 3+ consecutive deliveries fail in a 24h window. |
| createdAt | iso8601 | When the webhook was created. |
◆ Request
curl -X POST "https://api.coinmarketcal.com/v2/webhooks" \ -H "Authorization: Bearer $COINMARKETCAL_API_KEY" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "name": "example", "url": "example", "events": [ "example" ], "apiVersion": "example" }'
◆ Example response
{ "id": "wh_01HW8K2D5N", "name": "events-prod", "url": "https://api.acme.io/cmc/webhooks", "events": [ "events.created", "events.updated" ], "apiVersion": "2025-10-01", "signingSecret": "whsec_7e8f1a2b3c4d5e6f7e8f1a2b3c4d5e6f", "status": "healthy", "createdAt": "2026-04-12T08:30:00Z" }
/v2/webhooks/{id}Elite+Delete a webhook
Permanently removes the endpoint. In-flight deliveries already queued will not be sent. Returns 204 No Content on success.
◆ Parameters
| Field | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | The webhook id. |
◆ Response
| Field | Type | Description |
|---|---|---|
| — | string | No body. The `204` status is the success signal. |
◆ Request
curl -X DELETE "https://api.coinmarketcal.com/v2/webhooks/{id}" \ -H "Authorization: Bearer $COINMARKETCAL_API_KEY" \ -H "Accept: application/json"
◆ Example response
No body (empty response).