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
itempublishoruploaddeleteevents? - 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
401for a missing or malformed header or any mismatch,400for a body that isn't JSON,200once 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_digeston bytes (encode both sides; passingstrraises 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_type | event_type values | Sent When |
|---|---|---|
item | create, update, delete, publish, unpublish | A record changes state |
item_type | create, update, delete | A model changes (including its fields) |
upload | create, update, delete | A media asset changes |
build_trigger | deploy_started, deploy_succeeded, deploy_failed | A build trigger deploys |
environment | deploy_started, deploy_succeeded, deploy_failed | An environment deploys |
maintenance_mode | change | An admin turns maintenance mode on or off |
sso_user | create | An SSO user is added as a collaborator |
cda_cache_tags | invalidate | CDA 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
- references/overview.md - What DatoCMS webhooks are, events and payload shape
- references/setup.md - Create the webhook and set Basic auth
- references/verification.md - Credential check details, limits, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests