Webhooks
Get notified in real-time when things happen in your workspace.
Overview
Webhooks send HTTP POST requests to your server when events occur in GoPimi — tickets created, conversations updated, contacts changed, SLA breached. Each workspace can have multiple webhooks, each subscribed to specific event types.
Setting Up a Webhook
- Go to Settings → Webhooks in your workspace
- Click Add Webhook
- Enter your endpoint URL (must be HTTPS)
- Select the events you want to receive
- Save — GoPimi generates a signing secret for verification
You can also manage webhooks via the API:
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/gopimi",
"events": ["ticket.created", "ticket.updated"]
}' \
/api/v1/workspaces/WORKSPACE_ID/webhooks Event Types
17 event types across tickets, conversations, contacts, and SLA:
| Event | Triggered When |
|---|---|
ticket.created | New ticket created |
ticket.updated | Any ticket update (also fires alongside the specific events below) |
ticket.closed | Ticket status changed to closed |
ticket.reopened | Ticket status changed from closed back to open or pending |
ticket.assigned | Ticket agent changed |
ticket.replied | Agent posted an email reply on a ticket (internal notes do not fire it) |
ticket.tagged | Tag added to or removed from a ticket |
ticket.deleted | Ticket deleted |
conversation.created | New conversation created (shared or personal inbox) |
conversation.replied | Reply posted on a conversation |
conversation.closed | Conversation status changed to closed |
conversation.assigned | Conversation agent changed |
conversation.tagged | Tag added to or removed from a conversation |
contact.created | New contact created |
contact.updated | Contact fields changed |
contact.deleted | Contact deleted |
sla.breached | SLA first-response or resolution deadline missed (breach_type in the payload says which) |
Conversation events fire for both shared and personal inbox conversations. The payload includes an owner_id field — null for shared conversations, or the user ID of the owner for personal inbox conversations.
Payload Format
Every webhook delivery sends a JSON payload:
{
"event": "ticket.created",
"timestamp": "2026-03-26T14:30:00Z",
"workspace_id": 1,
"data": {
"id": 42,
"subject": "Ticket #42 - Cannot access dashboard",
"status": "open",
"contact": { "id": 5, "name": "Jane Doe", "email": "[email protected]" },
"agent": null,
"created_at": "2026-03-26T14:30:00Z"
}
} Verifying Signatures
Every delivery includes an X-GoPimi-Signature header — sha256= followed by the hex HMAC-SHA256 of the raw request body using your webhook's signing secret. Strip the prefix, recompute over the raw bytes, and compare in constant time before trusting the payload. See Helpdesk Webhooks: Get Ticket Events Into Your Own System for the redelivery handling that goes with it.
// PHP example
$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $payload, $webhookSecret);
// Header is "sha256=<hex>"; strip the prefix before comparing.
$sent = substr($_SERVER['HTTP_X_GOPIMI_SIGNATURE'] ?? '', strlen('sha256='));
if (!hash_equals($expected, $sent)) {
http_response_code(401);
exit('Invalid signature');
} // Node.js example (requestBody must be the raw bytes, not re-serialized JSON)
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(requestBody)
.digest('hex');
// Header is "sha256=<hex>"; strip the prefix, then compare in constant time.
const sent = String(req.headers['x-gopimi-signature'] || '').replace(/^sha256=/, '');
if (sent.length !== expected.length
|| !crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
} Retry Behavior
If your endpoint returns a non-2xx status or times out:
- GoPimi retries with exponential backoff
- Each delivery attempt is logged with status code and response body
- View delivery logs in Settings → Webhooks → [webhook] → Deliveries
Quotas
The number of webhooks per workspace is governed by your plan's resource quotas. Check Settings → Usage to see your current limits.
Frequently Asked Questions
How are webhook payloads signed?
Using HMAC-SHA256 of the full JSON body with the webhook's per-webhook secret, sent in the X-GoPimi-Signature header as sha256=<hex>. Verify this on your endpoint before trusting the payload.
How many times will a failed delivery retry?
Up to five times with exponential backoff. Every attempt is recorded in webhook_deliveries with HTTP status, response body, and timing.
Which HTTP status codes count as a successful delivery?
Any 2xx response. 3xx, 4xx, and 5xx responses trigger retry.
Where can I inspect past deliveries?
Call GET /workspaces/{id}/webhooks/{webhook}/deliveries to see every delivery attempt for a given webhook, with status, response, and timing.
Related
- Email Pipeline — How inbound emails trigger ticket/conversation creation
- SLA Policies — Configure the deadlines that trigger SLA breach events
- API Reference — Webhook CRUD endpoints