Webhooks

Webhooks let you receive real-time HTTP POST notifications when events occur in your project. Configure webhook endpoints in the dashboard under Project → Webhooks, or manage them programmatically with the API/SDK (requires the webhooks:manage scope): GET /api/v1/webhooks, POST /api/v1/webhooks, and DELETE /api/v1/webhooks/:id (SDK: helmdesk.webhooks.list() / .create() / .delete()). The signing secret is returned once, on create.

Available events

EventFired when
ticket.createdA new ticket is submitted (via portal, API, or SDK)
ticket.resolvedA ticket is marked as resolved
message.receivedA new message is added to a ticket
feedback.submittedA customer submits a post-resolution satisfaction rating
email.bouncedA transactional email bounces (recipient is auto-suppressed)

Payload format

Each webhook delivery sends a JSON POST request with the following structure:

{
  "event": "ticket.created",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "ticket": {
      "id": "uuid",
      "number": 42,
      "subject": "Can't reset password",
      "status": "open",
      "priority": "high"
    },
    "customer": {
      "email": "user@example.com",
      "name": "Jane Smith"
    }
  }
}

Headers

Every webhook delivery includes these headers:

HeaderDescription
Content-TypeAlways application/json
X-Helmdesk-EventThe event type (e.g. ticket.created)
X-Helmdesk-SignatureHMAC-SHA256 signature of the request body

Verifying signatures

Every delivery is signed with your webhook secret using HMAC-SHA256. Always verify the signature to ensure the request came from Helmdesk.

import { createHmac } from 'crypto'

function verifyWebhookSignature(
  body: string,
  signature: string,
  secret: string
): boolean {
  const expected = createHmac('sha256', secret)
    .update(body)
    .digest('hex')
  return signature === expected
}

// Express / Next.js API route example
export async function POST(req: Request) {
  const body = await req.text()
  const signature = req.headers.get('x-helmdesk-signature') ?? ''

  if (!verifyWebhookSignature(body, signature, WEBHOOK_SECRET)) {
    return new Response('Invalid signature', { status: 401 })
  }

  const payload = JSON.parse(body)

  switch (payload.event) {
    case 'ticket.created':
      // Handle new ticket
      break
    case 'ticket.resolved':
      // Handle resolved ticket
      break
    case 'message.received':
      // Handle new message
      break
  }

  return new Response('OK', { status: 200 })
}

Slack integration

Enable Slack format when creating a webhook to receive payloads formatted as Slack Block Kit messages. Point the webhook URL at a Slack incoming webhook to get formatted notifications in your channel.

// Slack-formatted payload example
{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*New Ticket Created*\n> *Subject:* Can't reset password\n> *#42*\n> *Customer:* user@example.com\n> *Priority:* high"
      }
    }
  ]
}

Delivery behavior

  • • Webhooks are delivered asynchronously (fire-and-forget) and do not block API responses
  • • Deliveries time out after 5 seconds
  • • All deliveries (successes and failures) are logged and visible in the dashboard
  • • Use the Test button in the dashboard to send a sample delivery to your endpoint