Webhooks
In this guide, we will look at how to register and consume webhooks to integrate your app with the ITSM platform. With webhooks, your app can react in real time when something happens — such as a ticket being created, an SLA breach, a triage completing, or a change request moving through approval.
The webhook model
The webhook model contains all the information about a registered webhook endpoint, including the target URL, subscribed event types, and its current status.
Properties
- Name
id- Type
- string
- Description
Unique identifier for the webhook. Prefixed with
whk_.
- Name
url- Type
- string
- Description
The URL that ITSM will send webhook events to. Must be HTTPS.
- Name
name- Type
- string
- Description
A human-readable name for the webhook.
- Name
events- Type
- array
- Description
An array of event types this webhook is subscribed to. Use
["*"]to subscribe to all events.
- Name
secret- Type
- string
- Description
The secret key used to generate HMAC-SHA256 signatures. Only returned on creation.
- Name
active- Type
- boolean
- Description
Whether the webhook is currently enabled and receiving events.
- Name
organization_id- Type
- string
- Description
The organization this webhook belongs to.
- Name
created_at- Type
- timestamp
- Description
Timestamp of when the webhook was registered.
- Name
updated_at- Type
- timestamp
- Description
Timestamp of when the webhook was last updated.
List all webhooks
This endpoint allows you to retrieve a paginated list of all webhooks registered for your organization.
Optional attributes
- Name
active- Type
- boolean
- Description
Filter webhooks by active status.
- Name
limit- Type
- integer
- Description
Limit the number of webhooks returned. Defaults to 10.
Request
curl -G https://api.itsm.example/v1/webhooks \
-H "Authorization: Bearer {token}" \
-d active=true \
-d limit=10
Response
{
"has_more": false,
"data": [
{
"id": "whk_abc123",
"url": "https://myapp.example/webhooks/itsm",
"name": "Production ticket alerts",
"events": ["ticket.created", "ticket.resolved", "sla.breached"],
"active": true,
"organization_id": "org_xyz",
"created_at": "2026-02-20T08:00:00Z",
"updated_at": "2026-02-20T08:00:00Z"
},
{
"id": "whk_def456"
// ...
}
]
}
Create a webhook
This endpoint allows you to register a new webhook for your organization. You must provide a target URL and at least one event type to subscribe to. The response will include a secret field — store this securely, as it will not be shown again.
Required attributes
- Name
url- Type
- string
- Description
The HTTPS URL to deliver webhook events to.
- Name
events- Type
- array
- Description
An array of event types to subscribe to. Use
["*"]for all events.
Optional attributes
- Name
name- Type
- string
- Description
A human-readable name for the webhook.
- Name
active- Type
- boolean
- Description
Whether the webhook should be active immediately. Defaults to
true.
Request
curl https://api.itsm.example/v1/webhooks \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://myapp.example/webhooks/itsm",
"name": "Production ticket alerts",
"events": ["ticket.created", "ticket.resolved", "sla.breached"]
}'
Response
{
"id": "whk_abc123",
"url": "https://myapp.example/webhooks/itsm",
"name": "Production ticket alerts",
"events": ["ticket.created", "ticket.resolved", "sla.breached"],
"secret": "whsec_5f3e8a1b9c2d4e6f7a8b9c0d1e2f3a4b",
"active": true,
"organization_id": "org_xyz",
"created_at": "2026-02-24T10:00:00Z",
"updated_at": "2026-02-24T10:00:00Z"
}
The secret is only returned once during creation. Store it securely — you will need it to verify webhook signatures.
Retrieve a webhook
This endpoint allows you to retrieve a webhook by its ID. The secret field is not included in the response — it is only returned at creation time.
Request
curl https://api.itsm.example/v1/webhooks/whk_abc123 \
-H "Authorization: Bearer {token}"
Response
{
"id": "whk_abc123",
"url": "https://myapp.example/webhooks/itsm",
"name": "Production ticket alerts",
"events": ["ticket.created", "ticket.resolved", "sla.breached"],
"active": true,
"organization_id": "org_xyz",
"created_at": "2026-02-20T08:00:00Z",
"updated_at": "2026-02-20T08:00:00Z"
}
Update a webhook
This endpoint allows you to update an existing webhook configuration. You can change the URL, subscribed events, name, or toggle the active state. Updating a webhook does not regenerate the secret.
Optional attributes
- Name
url- Type
- string
- Description
The HTTPS URL to deliver webhook events to.
- Name
name- Type
- string
- Description
A human-readable name for the webhook.
- Name
events- Type
- array
- Description
An array of event types to subscribe to. Replaces the existing list.
- Name
active- Type
- boolean
- Description
Whether the webhook is enabled.
Request
curl -X PUT https://api.itsm.example/v1/webhooks/whk_abc123 \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"events": ["ticket.created", "ticket.resolved", "sla.breached", "triage.completed"],
"name": "Production alerts (expanded)"
}'
Response
{
"id": "whk_abc123",
"url": "https://myapp.example/webhooks/itsm",
"name": "Production alerts (expanded)",
"events": ["ticket.created", "ticket.resolved", "sla.breached", "triage.completed"],
"active": true,
"organization_id": "org_xyz",
"created_at": "2026-02-20T08:00:00Z",
"updated_at": "2026-02-24T10:30:00Z"
}
Delete a webhook
This endpoint allows you to delete a webhook. Once deleted, no further events will be delivered to the webhook URL. Any pending deliveries will be cancelled.
Request
curl -X DELETE https://api.itsm.example/v1/webhooks/whk_abc123 \
-H "Authorization: Bearer {token}"
Test a webhook
This endpoint sends a test event to your webhook URL so you can verify that your endpoint is correctly configured and can process deliveries. The test event uses the webhook.test event type with a sample payload.
Request
curl -X POST https://api.itsm.example/v1/webhooks/whk_abc123/test \
-H "Authorization: Bearer {token}"
Response
{
"id": "dlv_test_001",
"webhook_id": "whk_abc123",
"event_type": "webhook.test",
"status": "delivered",
"response_code": 200,
"response_time_ms": 142,
"delivered_at": "2026-02-24T10:05:00Z"
}
Delivery history
This endpoint returns the delivery history for a specific webhook. Each delivery record includes the event type, HTTP response code, response time, and whether the delivery was successful. Use this to diagnose failed deliveries and monitor webhook health.
Optional attributes
- Name
status- Type
- string
- Description
Filter by delivery status:
delivered,failed, orpending.
- Name
event_type- Type
- string
- Description
Filter by event type, e.g.
ticket.created.
- Name
limit- Type
- integer
- Description
Limit the number of deliveries returned. Defaults to 20.
Request
curl -G https://api.itsm.example/v1/webhooks/whk_abc123/deliveries \
-H "Authorization: Bearer {token}" \
-d status=failed \
-d limit=5
Response
{
"has_more": true,
"data": [
{
"id": "dlv_789xyz",
"webhook_id": "whk_abc123",
"event_id": "evt_abc123",
"event_type": "ticket.created",
"status": "failed",
"response_code": 500,
"response_time_ms": 3021,
"attempt": 3,
"max_attempts": 3,
"next_retry_at": null,
"delivered_at": null,
"failed_at": "2026-02-24T10:02:45Z",
"created_at": "2026-02-24T10:00:00Z"
}
]
}
Consuming webhooks
When your app receives a webhook request from ITSM, check the type attribute to determine what event occurred. The first part of the event type indicates the resource category (e.g., ticket, triage, sla), and the second part describes the action.
Example webhook payload
{
"id": "evt_abc123",
"type": "ticket.created",
"timestamp": "2026-02-24T10:00:00Z",
"organizationId": "org_xyz",
"payload": {
"id": "tkt_123",
"title": "VPN connection issue",
"status": "new",
"priority": "high",
"type": "incident"
}
}
Your endpoint should return a 2xx status code within 30 seconds to acknowledge receipt. If your endpoint returns a non-2xx response or times out, ITSM will retry the delivery according to the retry behavior described below.
Event types
Ticket events
- Name
ticket.created- Description
A new ticket was created, either manually or through an integration.
- Name
ticket.updated- Description
A ticket fields were updated (title, description, priority, type, or custom fields).
- Name
ticket.status_changed- Description
A ticket status transitioned (e.g.,
newtoin_progress,in_progresstoresolved).
- Name
ticket.assigned- Description
A ticket was assigned or reassigned to an agent or team.
- Name
ticket.resolved- Description
A ticket was marked as resolved.
- Name
ticket.closed- Description
A ticket was closed (final state after resolution confirmation).
Triage events
- Name
triage.completed- Description
AI triage finished classifying a ticket with priority, category, and routing recommendation.
- Name
triage.auto_resolved- Description
AI triage determined the ticket could be auto-resolved and applied a resolution.
- Name
triage.escalated- Description
AI triage escalated a ticket to a human agent due to complexity or sensitivity.
Workflow events
- Name
workflow.started- Description
An automated workflow (Temporal) was started for a ticket or change request.
- Name
workflow.completed- Description
A workflow completed all steps successfully.
- Name
workflow.failed- Description
A workflow encountered an unrecoverable error.
SLA events
- Name
sla.warning- Description
A ticket is approaching its SLA deadline (configurable warning threshold).
- Name
sla.breached- Description
A ticket has exceeded its SLA response or resolution time.
Comment events
- Name
comment.added- Description
A comment was added to a ticket, either by an agent, customer, or the system.
Change management events
- Name
change.submitted- Description
A change request was submitted for review.
- Name
change.approved- Description
A change request was approved by the required approvers.
- Name
change.rejected- Description
A change request was rejected.
- Name
change.implemented- Description
A change request was successfully implemented in production.
Connector events
- Name
connector.synced- Description
An external connector (e.g., ServiceNow, Jira) completed a sync cycle successfully.
- Name
connector.error- Description
An external connector encountered an error during sync.
User events
- Name
user.created- Description
A new user was provisioned in the organization.
- Name
user.updated- Description
A user profile, role, or permissions were updated.
ticket.created
{
"id": "evt_abc123",
"type": "ticket.created",
"timestamp": "2026-02-24T10:00:00Z",
"organizationId": "org_xyz",
"payload": {
"id": "tkt_123",
"title": "VPN connection issue",
"status": "new",
"priority": "high",
"type": "incident"
}
}
triage.completed
{
"id": "evt_def456",
"type": "triage.completed",
"timestamp": "2026-02-24T10:00:05Z",
"organizationId": "org_xyz",
"payload": {
"ticket_id": "tkt_123",
"priority": "high",
"category": "network",
"subcategory": "vpn",
"confidence": 0.94,
"routing": {
"team": "network-ops",
"agent": "agent_891"
},
"model": "hpe-gpt-120b",
"processing_time_ms": 1240
}
}
sla.breached
{
"id": "evt_ghi789",
"type": "sla.breached",
"timestamp": "2026-02-24T14:00:00Z",
"organizationId": "org_xyz",
"payload": {
"ticket_id": "tkt_123",
"sla_policy": "P1 - Critical",
"metric": "resolution_time",
"target_minutes": 240,
"elapsed_minutes": 245,
"breach_severity": "minor"
}
}
change.approved
{
"id": "evt_jkl012",
"type": "change.approved",
"timestamp": "2026-02-24T16:30:00Z",
"organizationId": "org_xyz",
"payload": {
"id": "chg_456",
"title": "Upgrade VPN gateway firmware",
"risk_level": "medium",
"approved_by": ["user_111", "user_222"],
"scheduled_at": "2026-02-25T02:00:00Z"
}
}
Security
To verify that a webhook was sent by ITSM and not a malicious actor, every webhook request includes an x-itsm-signature header. This signature is an HMAC-SHA256 hash of the raw request body, computed using the webhook secret that was returned when you created the webhook. Always verify this signature before processing the event.
Verifying a request
const crypto = require('crypto')
const signature = req.headers['x-itsm-signature']
const payload = JSON.stringify(req.body)
const hash = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
if (crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(signature))) {
// Request is verified
} else {
// Request could not be verified
}
Always use constant-time comparison functions (timingSafeEqual, hmac.compare_digest, hash_equals) to prevent timing attacks. Never use === or == to compare signatures directly.
Keep your webhook secret safe. If you believe it has been compromised, delete the webhook and create a new one to receive a fresh secret. Do not commit your webhook secret to version control.
Retry behavior
When a webhook delivery fails (non-2xx response or timeout after 30 seconds), ITSM will automatically retry the delivery up to 3 times using exponential backoff.
Retry schedule
- Name
Attempt 1- Type
- immediate
- Description
The initial delivery attempt, sent immediately when the event occurs.
- Name
Attempt 2- Type
- after ~1 minute
- Description
First retry, sent approximately 1 minute after the initial failure.
- Name
Attempt 3- Type
- after ~5 minutes
- Description
Second retry, sent approximately 5 minutes after the previous failure.
After all retry attempts are exhausted, the delivery is marked as failed and no further attempts are made. You can monitor failed deliveries using the delivery history endpoint.
If a webhook consistently fails (more than 10 consecutive failed deliveries), ITSM will automatically disable the webhook and send a notification to the organization admin email. You can re-enable it from the dashboard or via the update endpoint after resolving the issue.
Failed delivery record
{
"id": "dlv_789xyz",
"webhook_id": "whk_abc123",
"event_id": "evt_abc123",
"event_type": "ticket.created",
"status": "failed",
"attempts": [
{
"attempt": 1,
"response_code": 500,
"response_time_ms": 1203,
"attempted_at": "2026-02-24T10:00:00Z"
},
{
"attempt": 2,
"response_code": 500,
"response_time_ms": 2105,
"attempted_at": "2026-02-24T10:01:02Z"
},
{
"attempt": 3,
"response_code": 500,
"response_time_ms": 3021,
"attempted_at": "2026-02-24T10:06:15Z"
}
],
"failed_at": "2026-02-24T10:06:15Z",
"created_at": "2026-02-24T10:00:00Z"
}
Best practices
To make the most of ITSM webhooks and build reliable integrations, follow these recommendations:
- Respond quickly. Return a
200status code as soon as you receive the webhook. Process the event asynchronously using a background job queue to avoid timeouts. - Handle duplicates. Use the event
idto deduplicate. In rare cases (e.g., network issues during delivery acknowledgement), the same event may be delivered more than once. - Verify signatures. Always validate the
x-itsm-signatureheader before processing any webhook payload. Reject requests with invalid signatures. - Subscribe selectively. Only subscribe to the event types your integration needs. This reduces unnecessary traffic and simplifies your handler logic.
- Monitor delivery health. Periodically check the delivery history endpoint or set up alerts for failed deliveries to catch integration issues early.
- Use HTTPS. Webhook URLs must use HTTPS. Plain HTTP endpoints will be rejected during registration.
Recommended handler pattern
import { createHmac, timingSafeEqual } from 'crypto'
import { enqueue } from './job-queue'
export async function handleWebhook(req, res) {
// 1. Verify signature
const signature = req.headers['x-itsm-signature']
const payload = JSON.stringify(req.body)
const hash = createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex')
if (!timingSafeEqual(Buffer.from(hash), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' })
}
// 2. Acknowledge immediately
res.status(200).json({ received: true })
// 3. Process asynchronously
await enqueue('webhook.process', {
eventId: req.body.id,
type: req.body.type,
payload: req.body.payload,
})
}