Agent skill
Doppler Webhooks Skill
Receive and verify Doppler webhooks. Use when setting up Doppler webhook handlers, debugging X-Doppler-Signature verification, or handling config.secrets.update events when secrets change in a project config.
Install this skill
npx skills add hookdeck/webhook-skills --skill doppler-webhooks
When to Use This Skill
- How do I receive Doppler webhooks?
- How do I verify the
X-Doppler-Signatureheader? - How do I handle
config.secrets.updateevents (restart or redeploy when secrets change)? - Why is my Doppler webhook signature verification failing?
- How do I verify a custom JSON payload from Doppler?
Verification (core)
Doppler signing is optional: it only happens if you enter a Secret on the webhook, so always set one. The X-Doppler-Signature header is sha256=<hex>, the HMAC-SHA256 of the raw request body keyed on that Secret. Custom JSON payloads are signed the same way.
Do not copy the docs' Node sample, which hashes JSON.stringify(req.body). Re-serialized JSON can differ from the bytes Doppler signed. Hash the raw bytes and compare timing-safe.
Node:
const crypto = require('crypto');
function verify(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(signatureHeader);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python:
import hmac, hashlib
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header or not secret:
return False
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature_header, expected)
In Express use express.raw({ type: '*/*' }) so the body stays a Buffer whatever the content type.
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types
Event (type field) | Description |
|---|---|
config.secrets.update | Secrets changed in a config. The only documented type; the default payload always carries it |
The default payload has type, config, project, workplace, and diff (added, removed, updated secret names). A custom JSON payload replaces all of this and has no type, so don't assume one.
Important Headers
| Header | Description |
|---|---|
X-Doppler-Signature | sha256=<hex> HMAC-SHA256 of the raw body. Only sent when a Secret is set |
Environment Variables
DOPPLER_WEBHOOK_SECRET=your_webhook_secret # The "Secret" field on the Doppler webhook
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 doppler --path /webhooks/doppler
Reference Materials
- references/overview.md - Doppler webhook concepts and payload
- references/setup.md - Configuration guide
- references/verification.md - Signature verification details