Webhooks

Webhooks send content events to your HTTPS endpoint. When a record is created, updated, or deleted — the CMS makes a signed POST request to the configured URL.

How to set up

  1. Open Webhooks in the sidebar.
  2. Click Add webhook.
  3. Fill in:
    • Name — a label for the list.
    • Endpoint URL — the HTTPS address where the CMS will POST.
    • Eventsentity.created, entity.updated, entity.deleted. Pick one or more.
    • Content types — scope filter. “All content types” = no filter.
    • Active — enable/disable delivery.
  4. Save. The server generates a signing secret and shows it once — copy it. It can only be recovered by rotating the key.

Events

Event When it fires
entity.created A new record of any type was created.
entity.updated An existing record was changed.
entity.deleted A record was deleted.

The content-type scope filter narrows delivery: if you select only blog-post, the webhook fires only for post changes, not every record in the project.

What the receiver gets

The CMS sends a POST with a JSON body:

{
  "id": "whev_a1b2c3",
  "tenant_id": "cf6efe09-...",
  "type": "entity.created",
  "entity_slug": "blog-post",
  "entity_id": "550e8400-...",
  "data": { "title": "Hello", "slug": "hello" },
  "user": { "id": "u-1", "username": "alice", "role": "editor" },
  "timestamp": "2026-08-11T12:00:00Z",
  "attempt": 1
}

Headers:

Header Contents
Content-Type application/json
X-Dynamica-Signature HMAC-SHA256 signature (see below)
X-Dynamica-Delivery Delivery ID — for idempotency
X-Dynamica-Event Event type (entity.created)

The prev_data field appears only on entity.updated — it carries the state before the change. It is absent from other events.

Verifying the signature

Every request is HMAC-SHA256 signed with the secret you received when creating the webhook. The signature is sent in the X-Dynamica-Signature header as t=<unix seconds>,v1=<hex HMAC>. The HMAC is computed over the string "<timestamp>.<raw request body>" — use the original bytes, do not re-serialize the JSON.

Receiver-side verification:

  1. Parse t (unix seconds) and v1 (hex HMAC).
  2. Reject if |now - t| > 5 minutes.
  3. Compute HMAC over the raw request body (do NOT re-serialize JSON!).
  4. Compare to v1 in constant time.

Example (Node.js):

const crypto = require('crypto');

function verify(secret, rawBody, header) {
  const [tPart, v1Part] = header.split(',');
  const ts = tPart.split('=')[1];
  const v1 = v1Part.split('=')[1];

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(v1)
  );
}

Delivery and retries

Parameter Value
Timeout 10 seconds
Attempts 8 (first + 7 retries)
Period ~24 hours
Concurrency 10 global, 3 per webhook

Backoff schedule: 1 min → 5 min → 30 min → 2 h → 6 h → 12 h → 24 h.

HTTP response code behavior:

Code Action
2xx Success. Delivery complete.
410 Webhook is deactivated. No retries.
4xx (other) Client error. No retries.
5xx Retry. Each failure increments the counter.
Timeout / network Retry.

After 8 failures the delivery lands in dead letter — terminal status.

Delivery log

In the admin, below the webhook list — a log of the last 50 deliveries. For each: status, event, HTTP response code, attempt, the sent payload, and the receiver’s response body.

Statuses:

  • Success — delivered (2xx).
  • Retry — waiting for the next attempt.
  • Pending — queued.
  • Dead letter — all attempts exhausted.
  • Circuit open — breaker suspended delivery (5 minutes).
  • Partial failure — delivered, but command processing incomplete.
  • Client error — 4xx, delivery halted.

Actions in the log:

  • Test — send a test delivery.
  • Replay — re-deliver a specific delivery.
  • Rotate secret — generate a new signing secret.

Circuit breaker

If the receiver consistently responds 5xx or doesn’t answer, the circuit breaker opens: delivery is suspended for 5 minutes. Triggers: 20 consecutive failures or ≥50% failures out of the last 50 requests.

Limits

The number of webhooks depends on the plan. Checked on creation — if the limit is exceeded, the webhook is not created.

What’s next