Webhooks

Webhooks allow you to receive real-time notifications when events occur in Support Station. This enables you to build integrations that respond to ticket updates, new messages, and more. On this page, we will look at how to create, retrieve, update, and delete webhooks, as well as view delivery history.

The webhook model

The webhook model contains configuration for receiving event notifications.

Properties

  • Name
    id
    Type
    string
    Description

    Unique identifier for the webhook (UUID).

  • Name
    name
    Type
    string
    Description

    Human-readable name for the webhook.

  • Name
    url
    Type
    string
    Description

    The public HTTP or HTTPS URL that will receive webhook POST requests. Private network addresses and redirects are not supported.

  • Name
    secret
    Type
    string
    Description

    HMAC-SHA256 signing secret (only returned once on creation).

  • Name
    events
    Type
    string[]
    Description

    Array of event types this webhook is subscribed to.

  • Name
    is_active
    Type
    boolean
    Description

    Whether the webhook is currently active.

  • Name
    last_delivery_at
    Type
    timestamp
    Description

    Timestamp of the last delivery attempt (nullable).

  • Name
    last_delivery_status
    Type
    string
    Description

    Status of the last delivery: success or failed (nullable).

  • Name
    created_at
    Type
    timestamp
    Description

    Timestamp of when the webhook was created.

  • Name
    updated_at
    Type
    timestamp
    Description

    Timestamp of when the webhook was last updated. Present on list, retrieve, and update responses. Create responses omit this field and the last-delivery fields.


Event Types

The following events can trigger webhook notifications:

  • Name
    ticket.created
    Description

    A new ticket was created.

  • Name
    ticket.updated
    Description

    A ticket was updated (status, priority, assignee, tags changed).

  • Name
    message.created
    Description

    A new message was added to a ticket.


GET/api/v1/webhooks

List all webhooks

Returns all webhooks in your organization without pagination metadata.

Required scope: webhooks:read

Request

GET
/api/v1/webhooks
curl https://api.supportstation.io/api/v1/webhooks \
  -H "Authorization: Bearer {token}"

Response

{
  "data": [
    {
      "id": "webhook-uuid",
      "name": "Production Webhook",
      "url": "https://example.com/webhook",
      "events": ["ticket.created", "ticket.updated"],
      "is_active": true,
      "last_delivery_at": "2025-01-15T10:00:00.000Z",
      "last_delivery_status": "success",
      "created_at": "2025-01-01T00:00:00.000Z",
      "updated_at": "2025-01-15T10:00:00.000Z"
    }
  ]
}

GET/api/v1/webhooks/:id

Retrieve a webhook

Returns a single webhook by ID.

Required scope: webhooks:read

Request

GET
/api/v1/webhooks/:id
curl https://api.supportstation.io/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer {token}"

Response

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Production Webhook",
    "url": "https://example.com/webhook",
    "events": ["ticket.created", "ticket.updated"],
    "is_active": true,
    "last_delivery_at": "2025-01-15T10:00:00.000Z",
    "last_delivery_status": "success",
    "created_at": "2025-01-01T00:00:00.000Z",
    "updated_at": "2025-01-15T10:00:00.000Z"
  }
}

POST/api/v1/webhooks

Create a webhook

Creates a new webhook. The webhook secret is only returned once in the response - make sure to store it securely for signature verification.

Required scope: webhooks:write

Required attributes

  • Name
    name
    Type
    string
    Description

    Human-readable name for the webhook (max 100 characters).

  • Name
    url
    Type
    string
    Description

    A public HTTP or HTTPS URL that will receive POST requests.

  • Name
    events
    Type
    string[]
    Description

    Array of events to subscribe to. At least one event is required.

Request

POST
/api/v1/webhooks
curl -X POST https://api.supportstation.io/api/v1/webhooks \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Integration",
    "url": "https://example.com/webhook",
    "events": ["ticket.created", "message.created"]
  }'

Response

{
  "data": {
    "id": "new-webhook-uuid",
    "name": "My Integration",
    "url": "https://example.com/webhook",
    "secret": "whsec_abc123def456...",
    "events": ["ticket.created", "message.created"],
    "is_active": true,
    "created_at": "2025-01-15T10:00:00.000Z"
  }
}

PATCH/api/v1/webhooks/:id

Update a webhook

Updates an existing webhook.

Required scope: webhooks:write

Optional attributes

  • Name
    name
    Type
    string
    Description

    New name, from 1 to 100 characters.

  • Name
    url
    Type
    string
    Description

    New URL for receiving events.

  • Name
    events
    Type
    string[]
    Description

    New list of events to subscribe to. At least one supported event is required.

  • Name
    is_active
    Type
    boolean
    Description

    Enable or disable the webhook.

Request

PATCH
/api/v1/webhooks/:id
curl -X PATCH https://api.supportstation.io/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["ticket.created", "ticket.updated", "message.created"],
    "is_active": true
  }'

Response

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "My Integration",
    "url": "https://example.com/webhook",
    "events": ["ticket.created", "ticket.updated", "message.created"],
    "is_active": true,
    "last_delivery_at": "2025-01-15T10:00:00.000Z",
    "last_delivery_status": "success",
    "created_at": "2025-01-01T00:00:00.000Z",
    "updated_at": "2025-01-15T12:00:00.000Z"
  }
}

DELETE/api/v1/webhooks/:id

Delete a webhook

Deletes a webhook. This will stop all event deliveries to this endpoint.

Required scope: webhooks:write

Request

DELETE
/api/v1/webhooks/:id
curl -X DELETE https://api.supportstation.io/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer {token}"

Response

{
  "data": {
    "deleted": true
  }
}

GET/api/v1/webhooks/:id/deliveries

Get delivery history

Returns the delivery history for a webhook, showing past delivery attempts and their status.

Required scope: webhooks:read

Query parameters

  • Name
    page
    Type
    integer
    Description

    Page number (default: 1).

  • Name
    limit
    Type
    integer
    Description

    Items per page, max 100 (default: 20).

Request

GET
/api/v1/webhooks/:id/deliveries
curl https://api.supportstation.io/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000/deliveries \
  -H "Authorization: Bearer {token}"

Response

{
  "data": [
    {
      "id": "delivery-uuid",
      "event": "ticket.created",
      "status": "success",
      "status_code": 200,
      "attempt_count": 1,
      "created_at": "2025-01-15T10:00:00.000Z",
      "completed_at": "2025-01-15T10:00:01.000Z"
    },
    {
      "id": "delivery-uuid-2",
      "event": "message.created",
      "status": "failed",
      "status_code": 500,
      "attempt_count": 5,
      "created_at": "2025-01-15T09:00:00.000Z",
      "completed_at": "2025-01-15T09:15:00.000Z"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 150,
      "total_pages": 8
    }
  }
}

Webhook Payload

Each webhook request includes:

  • Name
    event
    Type
    string
    Description

    The event type that triggered the webhook.

  • Name
    timestamp
    Type
    string
    Description

    ISO 8601 timestamp of when the event occurred.

  • Name
    organization_id
    Type
    string
    Description

    Your organization UUID.

  • Name
    data
    Type
    object
    Description

    The event data (varies by event type).

ticket.created payload

{
  "event": "ticket.created",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "organization_id": "org-uuid",
  "data": {
    "id": "ticket-uuid",
    "ticket_number": "42",
    "subject": "Help with login",
    "status": "OPEN",
    "priority": "MEDIUM",
    "channel": "API",
    "customer": {
      "id": "customer-uuid",
      "email": "[email protected]",
      "name": "John Doe"
    },
    "assignee": null,
    "team": null,
    "tags": [],
    "created_at": "2025-01-15T10:30:00.000Z",
    "updated_at": "2025-01-15T10:30:00.000Z",
    "closed_at": null
  }
}

Message Event Payload

When a new message is added to a ticket, the payload includes the message details along with ticket context.

message.created payload

{
  "event": "message.created",
  "timestamp": "2025-01-15T11:00:00.000Z",
  "organization_id": "org-uuid",
  "data": {
    "id": "message-uuid",
    "ticket_id": "ticket-uuid",
    "ticket_number": "42",
    "content": "Thanks for your help!",
    "type": "INBOUND",
    "author": {
      "type": "customer",
      "id": "customer-uuid",
      "email": "[email protected]",
      "name": "John Doe"
    },
    "created_at": "2025-01-15T11:00:00.000Z"
  }
}

Verifying Webhooks

To ensure webhook requests are genuinely from Support Station, each request includes a signature header that you can verify:

X-Webhook-Signature: sha256=abc123...
X-Webhook-Timestamp: 2025-01-15T10:30:00.000Z
X-Webhook-Event: ticket.created

Verify the signature over the raw request body using your webhook secret. Do not parse and re-serialize the JSON before verification.

Signature verification

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const secret = process.env.WEBHOOK_SECRET;
if (!secret) throw new Error('WEBHOOK_SECRET is required');

function verifyWebhook(payload, signature, secret) {
  if (typeof signature !== 'string') return false;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  const expected = Buffer.from(`sha256=${expectedSignature}`);
  const received = Buffer.from(signature);
  return expected.length === received.length &&
    crypto.timingSafeEqual(expected, received);
}

// Register this route before express.json() so the raw bytes are preserved.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = req.body;

  if (!verifyWebhook(payload, signature, secret)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the webhook
  const event = JSON.parse(payload.toString('utf8'));
  console.log('Received event:', event.event);

  res.status(200).send('OK');
});

Best Practices

  • Respond quickly: Return a 2xx response promptly. Delivery requests time out after 30 seconds. Process webhooks asynchronously if needed.
  • Handle duplicates: Webhooks may be delivered more than once. Deduplicate with the signed event, timestamp, and resource ID. The delivery ID is available in delivery history but is not sent in the webhook request.
  • Verify signatures: Always verify the webhook signature before processing.
  • Retry handling: Failed webhooks will be retried with exponential backoff (up to 5 total attempts, with retry delays of 1, 2, 4, and 8 minutes; queue delays can add time).
  • Circuit breaker: After 10 failed delivery attempts without a success, new events stop being queued. The is_active field does not change, and existing queued deliveries can still run. Set is_active to true or update the URL to reset the failure count.
POST/api/v1/webhooks/subscribe

Subscribe by URL

This route supports Zapier REST hooks. Required scope: tickets:write.

Provide a valid url and one supported event. It adds the event to a webhook with that URL or creates a new webhook. The response does not include a signing secret. Use POST /webhooks when you need the secret for signature checks.

Request body

{
  "url": "https://example.com/webhook",
  "event": "ticket.created"
}

Response (201)

{
  "data": {
    "id": "webhook-uuid",
    "url": "https://example.com/webhook",
    "event": "ticket.created"
  }
}
POST/api/v1/webhooks/unsubscribe

Unsubscribe by URL

Required scope: tickets:write.

Provide a valid url. Optionally provide one supported event to remove only that subscription. If no events remain, the webhook is deleted. Omit event to delete the webhook and all its subscriptions. An unknown URL or an event that is not subscribed returns success without changes.

Request body

{
  "url": "https://example.com/webhook",
  "event": "ticket.created"
}

Response (201)

{
  "data": { "success": true }
}

Was this page helpful?