Agent skill
Help Scout Webhooks Skill
Receive and verify Help Scout webhooks. Use when setting up Help Scout webhook handlers, debugging X-HelpScout-Signature verification failures, or handling support events like convo.created, convo.assigned, convo.customer.reply.created, customer.created, or satisfaction.ratings.
Install this skill
npx skills add hookdeck/webhook-skills --skill help-scout-webhooks
When to Use This Skill
- How do I receive Help Scout webhooks?
- How do I verify the
X-HelpScout-Signatureheader? - Why is my Help Scout webhook signature verification failing?
- How do I handle
convo.created,convo.assigned, orcustomer.createdevents? - What happens when my endpoint fails or returns 410?
Verification (core)
Help Scout signs the raw request body with HMAC-SHA1 keyed on the webhook secret key you set in the Webhooks app, and sends the base64 digest in X-HelpScout-Signature. The event name is in X-HelpScout-Event. There is no timestamp and no official Node or Python verification SDK, so verification is manual.
Node:
const crypto = require('crypto');
function verify(rawBody, signatureHeader, secret) {
if (!signatureHeader) return false;
const expected = crypto.createHmac('sha1', secret).update(rawBody).digest('base64');
try {
return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
} catch {
return false; // different lengths
}
}
Python:
import base64, hashlib, hmac
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header:
return False
expected = base64.b64encode(
hmac.new(secret.encode(), raw_body, hashlib.sha1).digest()
).decode()
return hmac.compare_digest(signature_header.encode(), expected.encode())
Test vector from the Help Scout docs: secret your secret key and body {"ticket":{"id":"1","number":"2"},"customer":{"id":"1","fname":"Jackie","lname":"Chan","email":"jackie.chan@somewhere.com","emails":["jackie.chan@somewhere.com"]}} produce I1KlvGppYqvFTJgJ9jezdQMDiyI=.
Important: Verify the exact bytes Help Scout sent. Read the body raw (
express.raw({ type: '*/*' }),request.text(),await request.body()) and never re-serialize parsed JSON.
Respond carefully: any 2xx is success. A 410 tells Help Scout to deactivate and delete the webhook, so return 400 (not 410) for a bad signature.
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types
Sent in the X-HelpScout-Event header (not in the body).
| Event | Description |
|---|---|
convo.created | Conversation created |
convo.assigned | Conversation assigned |
convo.status | Conversation status updated |
convo.customer.reply.created | Customer replied |
convo.agent.reply.created | Agent replied (user-generated threads only) |
customer.created | Customer created (global, not filterable by inbox) |
customer.updated | Customer updated |
satisfaction.ratings | Satisfaction rating received |
For the full list (notes, tags, merges, Beacon chat, organizations, users), see references/overview.md and the Help Scout webhooks docs.
Environment Variables
HELPSCOUT_WEBHOOK_SECRET=your_secret_key # Secret key set in Workspace > Apps > Webhooks
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 help-scout --path /webhooks/help-scout
Reference Materials
- references/overview.md - Help Scout webhook concepts, events, retries
- references/setup.md - Configure the Webhooks app and secret key
- references/verification.md - Signature verification details and gotchas