Agent skill
Typefully Webhooks Skill
Receive and verify Typefully webhooks. Use when setting up Typefully API v2 webhook handlers, debugging X-Typefully-Signature verification, or handling draft events like draft.created, draft.scheduled, draft.published, draft.status_changed, and draft.deleted.
Install this skill
npx skills add hookdeck/webhook-skills --skill typefully-webhooks
When to Use This Skill
- How do I receive Typefully webhooks?
- How do I verify the
X-Typefully-Signatureheader (sha256=<hex>)? - How do I handle
draft.publishedordraft.scheduledevents? - Why is my Typefully webhook signature verification failing?
- Which events does Typefully send, and how does it retry?
Essential Code (USE THIS)
Typefully (API v2) sends a JSON envelope { "event": "draft.created", "data": { ...draft } } and signs each delivery with three headers:
X-Typefully-Event: draft.published
X-Typefully-Timestamp: 1737381605 (Unix seconds)
X-Typefully-Signature: sha256=<64 hex chars>
- Algorithm: HMAC-SHA256, lowercase hex, keyed with the webhook secret (from your Typefully API settings) used as-is, as UTF-8.
- Signed message: the
X-Typefully-Timestampvalue, a literal., then the raw request body bytes. - Typefully's docs only say to check that the timestamp is recent; they don't give a window. The examples leave it off by default (see below).
Verification (Node.js)
const crypto = require('crypto');
// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyTypefully(rawBody, timestamp, signature, secret) {
if (!secret || !timestamp || !signature) return false;
const digest = signature.startsWith('sha256=') ? signature.slice(7) : signature;
if (!/^[0-9a-fA-F]{64}$/.test(digest)) return false; // validate before decoding
const expected = crypto.createHmac('sha256', secret)
.update(Buffer.concat([Buffer.from(timestamp, 'latin1'), Buffer.from('.'), rawBody]))
.digest();
const received = Buffer.from(digest.toLowerCase(), 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
Verification (Python)
import hashlib, hmac, re
def verify_typefully(raw_body: bytes, timestamp, signature, secret: str) -> bool:
if not secret or not timestamp or not signature:
return False
digest = signature[len("sha256="):] if signature.startswith("sha256=") else signature
if not re.fullmatch(r"[0-9a-fA-F]{64}", digest):
return False
message = b"".join([timestamp.encode("latin-1"), b".", raw_body])
expected = hmac.new(secret.encode(), message, hashlib.sha256).digest()
return hmac.compare_digest(bytes.fromhex(digest), expected)
Respond 401 when the signature or timestamp header is missing, malformed, or doesn't match. Respond with any 2xx once the event is accepted.
Replay window (opt-in): Typefully suggests checking that the timestamp is recent. The examples don't, by default: Hookdeck's retries and replays resend the original timestamp, and Typefully doesn't document whether its own retries do, so a fixed window could reject legitimate redeliveries. Set TYPEFULLY_ to a positive number of seconds (the timestamp is Unix seconds) to opt in; unset or 0 leaves it off. Use idempotency on data.id plus the event for duplicate protection.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
The type is in the body's event field (and the X-Typefully-Event header). The docs list seven:
| Event | Description |
|---|---|
draft.created | A new draft is created |
draft.planned | A draft is planned (dated but inert until confirmed) |
draft.scheduled | A draft is scheduled for publishing |
draft.published | A draft is successfully published |
draft.status_changed | Any status transition (includes planned, scheduled, published) |
draft.tags_changed | A draft's tags are modified |
draft.deleted | A draft is deleted |
data holds the full draft (id, social_set_id, status, scheduled_date, tags, per-platform platforms, published URLs such as x_published_url). See references/overview.md.
For the full event reference, see Typefully's API docs.
Important Headers
| Header | Description |
|---|---|
X-Typefully-Signature | sha256=<hex> HMAC-SHA256 signature |
X-Typefully-Timestamp | Unix timestamp (seconds) when the event was generated |
X-Typefully-Event | The event type, for example draft.published |
Environment Variables
TYPEFULLY_WEBHOOK_SECRET=your_typefully_webhook_secret # from Typefully's API settings
TYPEFULLY_WEBHOOK_TOLERANCE_SECONDS=0 # optional; a positive value opts in to the replay window
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 typefully --path /webhooks/typefully
Reference Materials
- references/overview.md - Typefully webhook concepts, events, payloads, retries
- references/setup.md - Getting the secret and registering your endpoint
- references/verification.md - Signature verification details and gotchas