Webhooks allow you to receive real-time notifications when events occur in your ticket system. Use them to integrate with services like Zapier, Make.com, Slack, Jira, or your own custom applications.
Quick Start
For most integrations (Zapier, Make.com, n8n, etc.), simply:
Go to Webhooks in your dashboard
Click Create Webhook
Enter the URL from your integration service
Select the events you want to receive
Save - you’re done!
No additional configuration needed for standard integrations.
Event Types
TicketCord supports 23 webhook events:
Ticket Events
Event Description ticket.createdA new ticket was created ticket.claimedStaff member claimed a ticket ticket.unclaimedTicket was unclaimed ticket.closedTicket was closed ticket.reopenedClosed ticket was reopened ticket.deletedTicket was deleted ticket.renamedTicket subject was changed
Message Events
Event Description message.createdNew message in a ticket message.editedMessage was edited message.deletedMessage was deleted
Priority & SLA Events
Event Description priority.changedTicket priority was updated sla.breachSLA time limit was exceeded sla.warningSLA is close to breaching sentiment.negativeNegative sentiment detected in ticket
Approval Events
Event Description approval.requestedApproval was requested approval.approvedApproval was granted approval.deniedApproval was denied
Ticket Notes
Event Description ticket.note.addedInternal note was added ticket.note.removedInternal note was removed
Access Control Events
Event Description ticket.user.addedUser was added to ticket ticket.user.removedUser was removed from ticket ticket.role.addedRole was added to ticket ticket.role.removedRole was removed from ticket
Payload Structure
All webhooks are delivered as POST requests with JSON payloads.
Base Structure
Every webhook follows this structure:
{
"event" : "ticket.created" ,
"timestamp" : 1702483200 ,
"data" : { ... },
"attemptNumber" : 1
}
Field Type Description eventstring Event type (e.g., ticket.created) timestampnumber Unix timestamp when event occurred dataobject Event-specific payload (see examples below) attemptNumbernumber Delivery attempt (1 = first try, 2+ = retries)
Payload Examples
{
"event" : "ticket.created" ,
"timestamp" : 1702483200 ,
"data" : {
"ticketId" : "507f1f77bcf86cd799439011" ,
"ticketNumber" : 42 ,
"channelId" : "111222333444555666" ,
"creatorId" : "123456789012345678" ,
"creatorName" : "John Doe" ,
"category" : "Billing" ,
"priority" : "medium" ,
"subject" : "Need help with billing" ,
"createdAt" : "2024-12-13T10:00:00.000Z"
},
"attemptNumber" : 1
}
{
"event" : "ticket.closed" ,
"timestamp" : 1702486800 ,
"data" : {
"ticketId" : "507f1f77bcf86cd799439011" ,
"ticketNumber" : 42 ,
"channelId" : "111222333444555666" ,
"creatorId" : "123456789012345678" ,
"creatorName" : "John Doe" ,
"closedAt" : "2024-12-13T11:00:00.000Z" ,
"closeReason" : "Issue resolved" ,
"staffMemberId" : "999888777666555444" ,
"staffName" : "Support Agent"
},
"attemptNumber" : 1
}
{
"event" : "ticket.claimed" ,
"timestamp" : 1702484000 ,
"data" : {
"ticketId" : "507f1f77bcf86cd799439011" ,
"ticketNumber" : 42 ,
"channelId" : "111222333444555666" ,
"creatorId" : "123456789012345678" ,
"claimerId" : "999888777666555444" ,
"claimerName" : "Support Agent"
},
"attemptNumber" : 1
}
{
"event" : "message.created" ,
"timestamp" : 1702484500 ,
"data" : {
"messageId" : "222333444555666777" ,
"ticketId" : "507f1f77bcf86cd799439011" ,
"channelId" : "111222333444555666" ,
"authorId" : "123456789012345678" ,
"authorName" : "John Doe" ,
"content" : "Hello, I need help with my subscription." ,
"embeds" : 0 ,
"attachments" : 1 ,
"isStaff" : false ,
"isBot" : false ,
"createdAt" : "2024-12-13T10:15:00.000Z"
},
"attemptNumber" : 1
}
{
"event" : "priority.changed" ,
"timestamp" : 1702485000 ,
"data" : {
"ticketId" : "507f1f77bcf86cd799439011" ,
"channelId" : "111222333444555666" ,
"oldPriority" : "medium" ,
"newPriority" : "high" ,
"changedBy" : "999888777666555444" ,
"changedByName" : "Support Agent" ,
"reason" : "Urgent billing issue" ,
"timestamp" : "2024-12-13T10:30:00.000Z"
},
"attemptNumber" : 1
}
Every webhook request includes these headers:
Header Description Content-TypeAlways application/json User-AgentTicketCord-Webhooks/1.0X-TicketCord-EventThe event type (e.g., ticket.created) X-TicketCord-TimestampUnix timestamp when request was created X-TicketCord-SignatureHMAC signature for verification
Retry Policy
Failed webhook deliveries are automatically retried:
Maximum retries : 3 attempts
Backoff : Exponential (1s, 2s, 4s)
Success codes : Any 2xx response is considered successful
Timeout : 30 seconds per request
Building a Custom Webhook Receiver
This section is for developers building custom integrations. Most users can skip this and use services like Zapier or Make.com.
Basic Receiver
A minimal webhook receiver that accepts events and responds quickly:
import express , { Request , Response } from 'express' ;
const app = express ();
app . use ( express . json ());
interface WebhookPayload {
event : string ;
timestamp : number ;
data : Record < string , unknown >;
attemptNumber : number ;
}
app . post ( '/webhooks/ticketcord' , ( req : Request , res : Response ) => {
const { event , data , attemptNumber } = req . body as WebhookPayload ;
console . log ( `Received: ${ event } (attempt ${ attemptNumber } )` );
// TODO: Process event (send to queue, database, etc.)
// Always respond quickly with 200 to acknowledge receipt
res . status ( 200 ). json ({ received: true });
});
app . listen ( 3000 );
Best Practice : Respond with 200 immediately, then process events asynchronously (via queue, background job, etc.) to avoid timeouts.
Signature Verification
For production systems, always verify that webhook requests actually come from TicketCord. This prevents attackers from sending fake events to your endpoint.
How Signatures Work
Each webhook request is signed using your webhook’s secret key:
TicketCord creates a signature string: v0:{timestamp}:{payload}
Generates HMAC-SHA256 hash using your secret
Sends the hash in the X-TicketCord-Signature header
Verification Steps
Extract the timestamp from X-TicketCord-Timestamp header
Get the raw JSON payload body (before parsing)
Compute: HMAC-SHA256("v0:{timestamp}:{payload}", your_secret)
Compare your computed signature with X-TicketCord-Signature
Verify timestamp is within 5 minutes (replay attack protection)
Why verify timestamps? Without timestamp validation, an attacker could intercept a valid webhook and replay it later. By checking the timestamp is recent, you ensure the request is fresh.
Complete Examples with Signature Verification
import crypto from 'crypto' ;
import express , { Request , Response } from 'express' ;
// Extend Express Request to include rawBody
declare global {
namespace Express {
interface Request {
rawBody ?: string ;
}
}
}
const app = express ();
// ============================================================
// IMPORTANT: Capture raw body BEFORE JSON parsing
// The signature is computed on the raw string, not parsed JSON
// ============================================================
app . use ( express . json ({
verify : ( req : Request , _res , buf ) => {
req . rawBody = buf . toString ();
}
}));
// Load your webhook secret from environment variables
const WEBHOOK_SECRET = process . env . TICKETCORD_WEBHOOK_SECRET ;
if ( ! WEBHOOK_SECRET ) {
console . error ( 'ERROR: TICKETCORD_WEBHOOK_SECRET environment variable not set' );
process . exit ( 1 );
}
/**
* Verify that a webhook request is authentic
*/
function verifySignature ( payload : string , timestamp : number , signature : string ) : boolean {
// STEP 1: Check timestamp freshness (prevent replay attacks)
const currentTime = Math . floor ( Date . now () / 1000 );
const age = Math . abs ( currentTime - timestamp );
if ( age > 300 ) {
console . warn ( `[Security] Request too old: ${ age } seconds` );
return false ;
}
// STEP 2: Compute expected signature
const signatureBase = `v0: ${ timestamp } : ${ payload } ` ;
const expectedSignature = crypto
. createHmac ( 'sha256' , WEBHOOK_SECRET )
. update ( signatureBase )
. digest ( 'hex' );
// STEP 3: Compare signatures using constant-time comparison
try {
return crypto . timingSafeEqual (
Buffer . from ( signature , 'hex' ),
Buffer . from ( expectedSignature , 'hex' )
);
} catch {
return false ;
}
}
app . post ( '/webhooks/ticketcord' , ( req : Request , res : Response ) => {
const signature = req . headers [ 'x-ticketcord-signature' ] as string ;
const timestampHeader = req . headers [ 'x-ticketcord-timestamp' ] as string ;
const eventType = req . headers [ 'x-ticketcord-event' ] as string ;
if ( ! signature || ! timestampHeader ) {
return res . status ( 401 ). json ({ error: 'Missing signature headers' });
}
const timestamp = parseInt ( timestampHeader , 10 );
if ( isNaN ( timestamp )) {
return res . status ( 401 ). json ({ error: 'Invalid timestamp' });
}
if ( ! verifySignature ( req . rawBody ! , timestamp , signature )) {
console . warn ( `[Security] Invalid signature for ${ eventType } event` );
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
console . log ( `[Webhook] Verified ${ eventType } event` );
// Process the event...
res . status ( 200 ). json ({ received: true });
});
const PORT = process . env . PORT || 3000 ;
app . listen ( PORT , () => {
console . log ( `Secure webhook server running on port ${ PORT } ` );
});
Security Best Practices
Always verify signatures in production to prevent unauthorized requests.
Always verify signatures - Don’t trust requests without valid signatures
Check timestamps - Reject requests older than 5 minutes to prevent replay attacks
Use HTTPS - Your webhook endpoint should only accept HTTPS connections
Use constant-time comparison - Prevents timing attacks when comparing signatures
Keep secrets secure - Store your webhook secret in environment variables, never in code
Handle retries gracefully - Your endpoint may receive the same event multiple times (use idempotency keys)
Respond quickly - Return a 2xx response within 30 seconds to avoid timeouts
Testing Your Receiver
Use the Test button in the TicketCord dashboard to send a test webhook to your endpoint. This sends a test.ping event:
{
"event" : "test.ping" ,
"timestamp" : 1702483200 ,
"data" : {
"message" : "This is a test webhook from TicketCord" ,
"testMode" : true
},
"attemptNumber" : 1
}
Debugging
Check the View Logs option in the dashboard to see:
Request/response details for each delivery
HTTP status codes returned by your endpoint
Error messages for failed deliveries
Retry attempts and timing
Need Help? Get instant support via private DM