Webhooks Overview
Webhooks allow you to receive monitoring alerts as HTTP POST requests to an endpoint you control, instead of (or in addition to) email or Slack. This makes it easy to integrate monitoring alerts directly into your own systems — pipe them into a custom dashboard, trigger downstream automation, or store them in your own database. When a development matches one of your monitoring views, Messari serializes the matched event as JSON and POSTs it to your configured URL. Each request can be signed with an HMAC signature so you can verify it came from Messari. This page covers how to configure webhook delivery, the payload format, signature verification, and retry behavior.Setting Up a Webhook
To enable webhook delivery for a monitoring view, create or update an alert policy through the UI and provide awebhook_url. Optionally include a webhook_secret to enable HMAC signature verification.
Fields
webhook_url(required whendelivery_typescontainswebhook) — the HTTP(S) endpoint Messari will POST to.webhook_secret(optional) — a shared secret used to compute the HMAC signature. We strongly recommend setting one so your endpoint can verify request authenticity. If omitted, theX-Webhook-Signatureheader will not be included.
Webhooks are only delivered for the
immediately cadence. A policy with daily or weekly cadence will not dispatch to webhook endpoints even if webhook is listed in delivery_types and a webhook_url is set.Request Format
Each webhook is delivered as an HTTPPOST with the following headers:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | MessariCNS/1.0 |
X-Webhook-Signature | HMAC signature (only present when webhook_secret is set) — see Verifying Signatures |
null. The example below shows a fully populated payload:
Envelope Fields
| Field | Type | Description |
|---|---|---|
id | string (UUID) | The CNS event ID. The same id is used across retries of a single delivery, and is shared across deliveries of the same event to multiple policies. Use it as an idempotency key on your side. |
type | string | The payload type. For monitoring alerts this is always monitoring_event. |
timestamp | string (RFC 3339) | When the event was created in Messari. |
data | object | The event payload — see below. |
Data Fields
| Field | Type | Required | Description |
|---|---|---|---|
view | object | yes | The monitoring view that matched (id, name). |
event | object | yes | The underlying Messari event the development belongs to (id, title). |
assets | array | optional | Assets extracted from the development (id, name, logo_url). Omitted when empty. |
latest_development | object | yes | The development that triggered the alert — see fields below. |
latest_development fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string (UUID) | yes | Development ID. |
title | string | yes | Development title. |
summary | string | yes | Development summary (Markdown). |
category | array of string | optional | Category taxonomy tags. Omitted when empty. |
subcategory | array of string | optional | Subcategory taxonomy tags. Omitted when empty. |
importance | string | optional | One of low, medium, high. Omitted when the development hasn’t been classified. |
actionable | bool | yes | Whether the development is marked actionable. Always present. |
created_at | string (RFC 3339) | yes | When the development started. Same value as started_at — included for compatibility. |
started_at | string (RFC 3339) | yes | When the development started. |
ended_at | string (RFC 3339) | optional | When the development ended. Omitted for ongoing developments. |
Verifying Signatures
When you provide awebhook_secret, every webhook delivery includes an X-Webhook-Signature header. Verify this signature on every incoming request to confirm the webhook came from Messari and was not tampered with.
Header Format
The signature header uses a Stripe-style format:t— the Unix timestamp (seconds) at which the signature was generatedv1— the HMAC-SHA256 signature as a hex string
Computing the Expected Signature
The signed payload is the concatenation of the timestamp, a literal., and the raw request body:
expected_sig against the v1 value from the header using a constant-time comparison. If they match, the request is authentic.
Rejecting Stale Signatures
A valid HMAC alone is not enough — without a freshness check, an attacker who captures a legitimate webhook could replay it indefinitely. Reject any request whoset timestamp is more than a small tolerance away from your server’s current time. We recommend a tolerance of 5 minutes, which comfortably covers network delay, clock skew, and the retry window described in Retries.
The helpers below combine both checks: they parse the header defensively (returning false on any missing or malformed component), enforce the timestamp tolerance, and then compare the HMAC in constant time.
Responding to Webhooks
Your endpoint should return an HTTP2xx status code to acknowledge successful receipt. The HTTP client has a 30-second transport timeout, and each delivery attempt has a 1-minute overall timeout. Slow or non-2xx responses may trigger retries (see below).
| Status code | Treated as |
|---|---|
2xx | Success |
429 | Retryable (rate limit) |
4xx (other) | Non-retryable failure |
5xx | Retryable failure |
| network error / timeout | Retryable failure |
200 OK quickly after durably queuing the alert for processing on your side, rather than doing heavy work synchronously.
Retries
When a webhook delivery fails with a retryable status code, network error, or timeout, Messari automatically retries with exponential backoff:- Maximum attempts: 5
- Initial interval: 1 second
- Backoff coefficient: 2×
- Maximum interval: 5 minutes
4xx responses (excluding 429) are not retried — if your endpoint returns 400 Bad Request, the delivery is permanently marked as failed.
Best Practices
- Always set a
webhook_secretand verify theX-Webhook-Signatureheader on every request, including the timestamp freshness check to prevent replay attacks. - Use HTTPS for your webhook URL. Plain HTTP endpoints are accepted but strongly discouraged.
- Make your handler idempotent. Retries reuse the same envelope
id; use it as an idempotency key on your side to avoid double-processing the same event. - Respond quickly. Return
2xxas soon as you have durably queued the event; do downstream processing asynchronously. - Treat the body as authoritative. Do not trust query strings or other unsigned inputs.
Example: Creating Tickets on Linear
This walkthrough builds a complete webhook handler that receives a Messari monitoring alert, verifies its signature, acknowledges receipt, and creates a Linear issue in the background. The same scaffolding works for any downstream destination — swap the Linear call for Slack, PagerDuty, an internal API, or a queue. The handler does five things in order:- Captures the raw request body (signatures are computed over raw bytes, not re-serialized JSON).
- Verifies the
X-Webhook-Signatureheader. - Returns
200 OKimmediately so Messari isn’t waiting on Linear’s response. - Hands the envelope to a background worker.
- Maps the alert into a Linear
issueCreatemutation and posts it.
The Server
Instead of running a server, you can also use no-code software like Zapier or n8n to handle and transform Messari’s webhook requests.verifyMessariSignature and verify_messari_signature are the helpers from Verifying Signatures above.
Transforming and Posting to Linear
Linear exposes a GraphQL API; new issues are created with theissueCreate mutation. The transform takes a Messari monitoring_event envelope and produces an IssueCreateInput — mapping the development title to the issue title, building a Markdown description from the view, asset list, and development summary, and translating Messari’s importance to Linear’s priority enum.
Two pieces of config are needed: a Linear API key for the Authorization header and the target team ID.
Linear personal API keys are passed directly in the
Authorization header (no Bearer prefix). OAuth access tokens use Authorization: Bearer <token>.
