Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.chatblocks.ai/llms.txt

Use this file to discover all available pages before exploring further.

The webhook connector accepts JSON payloads from your own systems. Configure a data source, point your app at the resulting URL with the signing secret, and the platform stores the payloads — bound blocks read them via webhook.latest, webhook.aggregate, and webhook.list queries. This is the “push from anywhere” connector. Use it when you have a service that emits events (cron job, status checker, Slack workflow) and you want the platform to display the latest values.

Add a webhook data source

1

Go to the wizard

/settings/data-sources/new/webhook.
2

Pick a label and save

The wizard creates the source and returns a single-use secret-reveal panel with the signing secret in plaintext. You’ll see it exactly once. Store it in your app’s secret manager.
3

Point your app at the URL

The data-sources list shows the receiver URL for each webhook source:
https://<convex-deployment>.convex.cloud/webhooks/inbound/<dataSourceId>
POST JSON with the signature headers below.
If you lose the secret, click Rotate secret on the data-sources list — it re-opens the same one-time-reveal modal with a fresh secret.

Wire format

Every inbound request needs three things: a signature header, an idempotency key, and a JSON body under 256 KB.

Headers

HeaderRequiredFormat
X-ChatBlocks-Signatureyest=<unix>,v1=<hex> (Stripe-style; leaves room for a v2 scheme).
Idempotency-KeyyesAny unique string per logical event. Reject without it (400).
Content-Typeyesapplication/json.

Signing

The signed payload is <unix>.<body>, HMAC-SHA256 with the per-source signing secret, hex-encoded.
import { createHmac } from "node:crypto";

const timestamp = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify({ value: 42, label: "Active sessions" });
const signature = createHmac("sha256", secret)
  .update(`${timestamp}.${body}`)
  .digest("hex");

await fetch(url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
    "X-ChatBlocks-Signature": `t=${timestamp},v1=${signature}`,
  },
  body,
});

Limits and policies

ConstraintValue
Body size256 KB
Replay window5 minutes
Clock skew tolerance±60 seconds
Idempotency-Keyrequired; dedup via webhookEvents
Storage cap per source1000 most recent payloads (enforced inline on insert)
Signature verification uses Web Crypto (crypto.subtle.importKey + subtle.verify) — Convex’s V8 runtime constraint. Reject 401 on signature mismatch, 400 on missing/malformed headers, 413 on oversize body.

Query catalog

Three first-class queries against stored payloads (see apps/web/convex/lib/connectors/webhookQueries.ts):
Returns the most recent payload’s body. No params.Result: the raw JSON body of the latest payload (or null if none).Use for “show me what just happened.”
Folds numeric values across payloads in a time window.
{
  "type": "webhook.aggregate",
  "params": {
    "window": "1h",
    "field": "value",
    "op": "sum"
  }
}
window is one of "15m", "1h", "24h", "7d". op is "sum", "count", or "avg". Numeric coercion via Number(x); null and NaN values are skipped.Result:
{ value: number; count: number; window: string; op: string }
Returns the N most recent payloads.
{
  "type": "webhook.list",
  "params": {
    "limit": 10,
    "sortBy": "timestamp",
    "order": "desc"
  }
}
sortBy is an optional in-memory JSONPath sort over the payloads; order is "asc" or "desc". Default order: most recent first.

Bind a block

{
  "binding": {
    "dataSourceId": "<webhook-data-source-id>",
    "queryConfig": {
      "type": "webhook.aggregate",
      "params": { "window": "1h", "field": "errors", "op": "sum" }
    },
    "projection": {
      "fields": {
        "value": { "source": "value", "format": "number" },
        "label": { "literal": "Errors (1h)" }
      }
    }
  },
  "widget": {
    "template": "metric",
    "data": { "value": "0", "label": "Errors (1h)" }
  }
}
The aggregator runs the query, applies the projection, writes widget.data. On every inbound POST the receiver schedules a refresh — push-mode by default.

Rotating and deleting

  • Rotate secret — generates a fresh signing secret and shows it once. Old secret stops validating immediately.
  • Delete source — sweeps the source’s payloads and event-dedupe rows in addition to the source row itself. Bound blocks lose their data feed; the binding stays on the manifest until you update the block.

What’s next

Outbound MCP

Connect to a third-party MCP server.

Audit log

Every payload + decrypt is logged with the calling actor.