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-Signature header?
  • Why is my Help Scout webhook signature verification failing?
  • How do I handle convo.created, convo.assigned, or customer.created events?
  • 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).

EventDescription
convo.createdConversation created
convo.assignedConversation assigned
convo.statusConversation status updated
convo.customer.reply.createdCustomer replied
convo.agent.reply.createdAgent replied (user-generated threads only)
customer.createdCustomer created (global, not filterable by inbox)
customer.updatedCustomer updated
satisfaction.ratingsSatisfaction 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →