Webhooks
Capture and send Help Buddy events to any external URL
Premium Feature
Overview
Outbound webhooks enable Help Buddy to send real-time updates (ticket open, message posting, SLA violation, etc) using HTTP.
This feature requires a Custom Buddy and Manage Server permissions in this guild.
How it works
- You create one or more endpoints (a URL + the events it should receive).
- When a subscribed event fires, Help Buddy renders your message body and
POSTs it to the endpoint. - Each request is signed so you can verify it genuinely came from Help Buddy.
- Failed deliveries are retried with exponential backoff; every attempt is visible in the delivery log.
Creating an endpoint
- Open Developers → Webhooks and click Add endpoint.
- Give it a name and paste the Payload URL that will receive events.
- Pick a format preset (Discord, Slack, Google Chat, Microsoft Teams, or Other/Raw).
- Check the events you want delivered.
- Optionally customize the message body, add custom headers, and use the live preview.
- Save. The signing secret is shown once on creation, copy it now.
Events
Subscribe an endpoint to any combination of these events:
| Group | Event | Fires when |
|---|---|---|
| Ticket lifecycle | ticket.created | A new ticket is opened |
ticket.claimed | A staff member claims / is assigned a ticket | |
ticket.closed | A ticket is closed | |
ticket.deleted | A ticket is deleted (from the bot or the dashboard) | |
| Messages | ticket.message | A staff or user message is posted in a ticket |
| SLA | sla.first_response_breached | The first-response SLA deadline is exceeded |
sla.subsequent_breached | A subsequent-response SLA deadline is exceeded | |
| CSAT | csat.submitted | A ticket creator submits a satisfaction rating |
| Reports | report.generated | A scheduled report has been generated |
| Config | panel.updated | A ticket panel is created or updated |
category.updated | A ticket category is created or updated | |
subscription.changed | The guild subscription tier or status changes (admin panel) |
Payload format
If you leave the message body blank (the Other (Raw) preset), Help Buddy sends a standard JSON envelope. The event-specific fields live under data:
{
"id": "evt_1042",
"event": "ticket.created",
"created": 1751500000,
"guild_id": "123456789012345678",
"data": {
"ticket_id": "998877665544332211",
"ticket_name": "ticket-0042",
"category": "billing",
"tier": "custom",
"creator": "ada",
"creator_id": "223344556677889900"
}
}Every request also carries these headers:
| Header | Value |
|---|---|
X-HelpBuddy-Event | The event name, e.g. ticket.created |
X-HelpBuddy-Delivery | Unique delivery id (also the retry key) |
X-HelpBuddy-Signature | HMAC signature, format t=<timestamp>,v1=<hex> |
Placeholders
Anywhere in a message body you can use {token} placeholders. They are replaced with real values at delivery time. An unknown token is left untouched so a typo is easy to spot. These are all the available placeholders:
| Always available | Meaning |
|---|---|
{id} | Unique delivery id (e.g. evt_1234) |
{event} | The raw event name, e.g. ticket.created |
{event_title} | Human-friendly event name, e.g. Ticket Created |
{created} | Unix timestamp (seconds) of delivery |
{guild_id} | The Discord server id |
| Ticket events | Meaning |
|---|---|
{ticket_id} | The ticket channel/thread id |
{ticket_name} | The ticket channel name |
{ticket_url} | Deep link to the ticket in Discord |
{category} | The ticket category / type |
{tier} | The tier the ticket was opened under |
{creator} | The ticket opener display name (on create) |
{creator_id} | The ticket opener user id |
{assigned_to} | Assigned staff user id (claim/close) |
| ticket.message | Meaning |
|---|---|
{message_content} | The message text |
{message_author} | Author username |
{message_author_id} | Author user id |
{message_role} | "staff" or "user" |
{message_id} | The Discord message id |
| SLA events | Meaning |
|---|---|
{sla_type} | "first_response" or "subsequent_response" |
{sla_deadline} | The configured deadline, e.g. "30 minutes" |
| csat.submitted | Meaning |
|---|---|
{rating} | The rating: "up", "neutral", or "down" |
{comment} | The optional free-text comment (empty if none) |
{source} | Where the rating came from: "discord" or "email" |
{staff} | The handling staff user id (empty if unclaimed) |
| Config events | Meaning |
|---|---|
{panel_id} | panel.updated: the panel id |
{panel_name} | panel.updated: the panel name |
{category_key} | category.updated: the category key |
{category_label} | category.updated: the category label |
{status} | subscription.changed: the subscription status |
{processor} | subscription.changed: the payment processor |
{ticket_url} is generated automatically for any ticket event. Tokens that do not apply to a given event resolve to an empty string.
Message customization
Each endpoint has a Use the same message for all events toggle:
- On - one shared body template is used for every subscribed event.
- Off - you author a separate body per event; events you leave blank fall back to the default envelope.
The signature is always computed over the final rendered body, so custom shapes still verify correctly. You can also set a custom Content-Type and add arbitrary request headers (for example anAuthorization header your receiver requires).
Platform setup
A preset seeds a starter body shaped for that platform's incoming webhook. You can edit it freely afterward. Presets are offered in this order: Discord, Slack, Google Chat, Microsoft Teams, Other (Raw).
Discord
- In Discord, open the target channel and choose Edit Channel → Integrations → Webhooks → New Webhook (requires the Manage Webhooks permission).
- Click Copy Webhook URL.
- In the dashboard, add an endpoint, paste that URL, choose your events, and select the Discord preset.
A Discord webhook only accepts Discord's own JSON shape, so the Discord preset is what makes events render as messages. Discord ignores the signature header, which is expected.
Slack
- Create (or open) a Slack app, enable Incoming Webhooks, and choose Add New Webhook to Workspace.
- Pick the destination channel and copy the generated URL.
- Add an endpoint, paste the URL, choose your events, and select the Slack preset.
Google Chat
- In the Google Chat space, open Apps & integrations → Manage webhooks.
- Add a webhook, give it a name, and copy the URL.
- Add an endpoint, paste the URL, choose your events, and select the Google Chat preset.
Microsoft Teams
- In the channel, choose … → Connectors → Incoming Webhook → Configure, name it, and copy the URL.
- Add an endpoint, paste the URL, choose your events, and select the Microsoft Teams preset.
Microsoft is migrating Teams from classic Office 365 connectors to Power Automate Workflows, which expect an Adaptive Card body instead of a MessageCard. If your tenant uses Workflows, create a "Post to a channel when a webhook request is received" flow and use the Other (Raw)preset with an Adaptive Card body.
Verifying the signature
Compute an HMAC-SHA256 over `${timestamp}.${rawBody}` using your endpoint's signing secret and compare it to the v1 value in the X-HelpBuddy-Signature header. Always use the raw request body (not a re-serialized object).
Node.js
const crypto = require('crypto');
function verify(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map(p => p.split('='))
); // { t, v1 }
const expected = crypto
.createHmac('sha256', secret)
.update(parts.t + '.' + rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(parts.v1)
);
}Python
import hmac, hashlib
def verify(raw_body: str, signature_header: str, secret: str) -> bool:
parts = dict(p.split('=') for p in signature_header.split(','))
expected = hmac.new(
secret.encode(), f"{parts['t']}.{raw_body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, parts['v1'])You may also reject requests whose t timestamp is far from the current time to guard against replays.
Retries and failures
A delivery succeeds on any 2xx response within 10 seconds. Otherwise it is retried on this schedule: 1m, 5m, 30m, 2h, 6h. After 6 failed attempts the delivery is marked dead. An endpoint that accumulates 15 consecutive dead deliveries is automatically disabled; re-enable it from the dashboard once your receiver is healthy. Every attempt, status code, and error is shown in Recent deliveries.
Testing
Use the Send test event button (the paper-plane icon) on any endpoint to queue a synthetic test.ping delivery. It is delivered within about 10 seconds and appears in the delivery log. A free service like webhook.site is handy for inspecting the exact payload and headers.