Agent skill
Twenty Webhooks Skill
Receive and verify Twenty CRM webhooks (cloud or self-hosted). Use when setting up Twenty webhook handlers, debugging X-Twenty-Webhook-Signature verification, or handling record events such as person.created, company.updated, opportunity.updated, note.created and person.deleted.
Install this skill
npx skills add hookdeck/webhook-skills --skill twenty-webhooks
When to Use This Skill
- How do I receive Twenty (open-source CRM) webhooks?
- How do I verify the
X-Twenty-Webhook-Signatureheader? - How do I handle
person.created,company.updatedoropportunity.updatedevents? - How do I receive webhooks for custom objects in Twenty?
- Why is my Twenty webhook signature verification failing?
Set a secret on the Twenty webhook (Settings > Developers > Webhooks). Twenty signs deliveries only when the webhook has a secret.
Verification (core)
Every delivery is a POST with X-Twenty-Webhook-Signature, X-Twenty-Webhook-Timestamp and X-Twenty-Webhook-Nonce. The signature is the bare lowercase hex HMAC-SHA256 (no prefix), keyed with the webhook secret (its UTF-8 bytes, used as-is), over the timestamp header value, a literal colon, then the raw request body. The nonce is sent but not signed.
const crypto = require('crypto');
function verifyTwentySignature(rawBody, signature, timestamp, secret) {
if (!signature || !timestamp || !secret) return false;
if (!/^[0-9a-fA-F]{64}$/.test(signature)) return false; // validate before decoding
const signed = Buffer.concat([Buffer.from(timestamp, 'latin1'), Buffer.from(':'), rawBody]); // bytes, never a re-encoded string
const expected = crypto.createHmac('sha256', secret).update(signed).digest();
const received = Buffer.from(signature.toLowerCase(), 'hex');
return received.length === expected.length && crypto.timingSafeEqual(expected, received);
}
Respond 401 for a missing, malformed or mismatched signature and any 2xx (the examples use 200) for a valid delivery. Read the raw body first (express.raw({ type: '*/*' }), await request.arrayBuffer(), await request.body()) and parse JSON only after the check. Twenty's docs example signs JSON.stringify(req.body); Twenty's sender signs the JSON string it posts, so signing the raw body is equivalent and avoids re-serialisation drift.
Timestamp window is off by default. X-Twenty-Webhook-Timestamp is Date.now() (Unix milliseconds). Hookdeck retries and replays resend the original headers and timestamp (and Hookdeck applies no tolerance itself), so a fixed window would reject legitimate retries. The examples offer an opt-in: set TWENTY_ to enforce one (unset or 0 = off). Use idempotency instead (see below).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
Twenty sends one event per record change, for every object including custom objects. Dispatch on the body's eventName (<object>.<action>); Twenty's docs page shows the same value in a field named event, so the examples read eventName and fall back to event.
| Event | Triggered When |
|---|---|
person.created | A person record is created |
person.deleted | A person record is deleted |
company.updated | A company record is updated |
opportunity.updated | An opportunity record is updated |
note.created | A note is created |
The same .created, .updated and .deleted actions fire for every other object (e.g. company.created, opportunity.deleted, or your custom objects). All events go to every webhook URL; there is no event filter, so ignore the ones you don't need.
Twenty's docs say a 2xx acknowledges a delivery and non-2xx is logged as a failure; the sender's HTTP timeout is 5 seconds and retries are not documented.
Environment Variables
# Secret set on the webhook in Twenty (Settings > Developers > Webhooks)
TWENTY_WEBHOOK_SECRET=your_twenty_webhook_secret
# Optional: reject deliveries older than N seconds (unset or 0 = off)
TWENTY_WEBHOOK_TOLERANCE_SECONDS=0
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 twenty --path /webhooks/twenty
Use the printed public HTTPS URL as the webhook URL in Twenty (Settings > Developers > Webhooks).
Reference Materials
- references/overview.md - How Twenty webhooks work, events, payload shape
- references/setup.md - Create the webhook and set its secret
- references/verification.md - Signature verification details and gotchas