Agent skill

DatoCMS Webhooks Skill

Receive and authenticate DatoCMS webhooks. Use when setting up a DatoCMS webhook endpoint, checking the HTTP Basic auth credentials DatoCMS sends, debugging 401s, or handling item create, update, publish and unpublish events, upload events and deploy events.

Install this skill

npx skills add hookdeck/webhook-skills --skill datocms-webhooks


DatoCMS sends an HTTP POST when content or project state changes: a record is created, updated, published or deleted, an upload is added, a build deploys. DatoCMS webhooks are not signed and carry no timestamp. You can attach HTTP Basic credentials (http_basic_user / http_basic_password) and custom headers to a webhook, and your endpoint authenticates the request by checking the Authorization: Basic header against the credentials you configured.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Anyone who captures a request can replay it, and the credential says nothing about whether the payload was altered. Serve the endpoint over HTTPS only, keep the credentials secret, and rotate them if they leak.

When to Use This Skill

  • How do I receive DatoCMS webhooks?
  • How do I verify DatoCMS webhook requests? (They aren't signed; use HTTP Basic auth)
  • How do I check the Basic auth username and password DatoCMS sends?
  • How do I handle item publish or upload delete events?
  • Why does my DatoCMS webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length digests, so timingSafeEqual never throws.
const digest = (v) => crypto.createHash('sha256').update(v).digest();
const safeEqual = (a, b) => crypto.timingSafeEqual(digest(a), digest(b));

function verifyDatoCmsRequest(headers, user = process.env.DATOCMS_WEBHOOK_USERNAME,
                              pass = process.env.DATOCMS_WEBHOOK_PASSWORD) {
  if (!user || !pass) return false;                         // never accept when unconfigured
  const m = /^basic\s+([A-Za-z0-9+/]+={0,2})\s*$/i.exec(headers['authorization'] || '');
  if (!m) return false;                                     // missing or malformed
  const decoded = Buffer.from(m[1], 'base64').toString('utf8');
  const i = decoded.indexOf(':');                           // split on the FIRST colon
  if (i < 0) return false;
  const userOk = safeEqual(decoded.slice(0, i), user);      // evaluate both: no short-circuit
  const passOk = safeEqual(decoded.slice(i + 1), pass);
  return userOk && passOk;
}
  • Header: Authorization: Basic base64(username:password). The scheme is case-insensitive and the password may itself contain colons, so split on the first colon only.
  • Return 401 for a missing or malformed header or any mismatch, 400 for a body that isn't JSON, 200 once handled.
  • Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse JSON only after the credential check. DatoCMS lets you send a non-JSON custom payload.
  • Python: hmac.compare_digest on bytes (encode both sides; passing str raises on non-ASCII).

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

The default body has entity_type, event_type, entity, environment and more. A handler dispatches on the pair.

entity_typeevent_type valuesSent When
itemcreate, update, delete, publish, unpublishA record changes state
item_typecreate, update, deleteA model changes (including its fields)
uploadcreate, update, deleteA media asset changes
build_triggerdeploy_started, deploy_succeeded, deploy_failedA build trigger deploys
environmentdeploy_started, deploy_succeeded, deploy_failedAn environment deploys
maintenance_modechangeAn admin turns maintenance mode on or off
sso_usercreateAn SSO user is added as a collaborator
cda_cache_tagsinvalidateCDA cache tags need invalidating

Record updates also include previous_entity. See references/overview.md for the payload and the draft/publish event sequences.

Environment Variables

DATOCMS_WEBHOOK_USERNAME=your-basic-auth-username   # the webhook's HTTP basic auth user
DATOCMS_WEBHOOK_PASSWORD=your-basic-auth-password   # the webhook's HTTP basic auth password

Local Development

DatoCMS can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 datocms --path /webhooks/datocms

Use the printed URL as the webhook URL, with the same username and password, in DatoCMS.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →