Webhooks
Receive real-time notifications when events occur in your projects.
Overview
Webhooks allow you to receive real-time HTTP notifications when events occur in your BugToMe projects. Instead of polling the API for changes, webhooks push data to your server as events happen.
Supported Events
BugToMe supports the following webhook events:
| Event | Description |
|---|---|
ticket_created |
A new ticket has been created |
ticket_status_changed |
A ticket's status has changed (e.g., open → in_progress) |
ticket_assigned |
A ticket has been assigned to a team member |
ticket_priority_changed |
A ticket's priority has been updated |
ticket_commented |
A comment has been added to a ticket |
ticket_closed |
A ticket has been closed |
Webhook Payload
All webhook payloads follow a consistent structure:
{
"event": "ticket_created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"ticket": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"code": "PROJ-42",
"title": "Login button not working",
"status": "open",
"priority": "high",
"given": "User is on the login page",
"when": "User clicks the login button",
"then": "Nothing happens",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"url": "https://bugto.me/workspace/projects/proj-id/tickets/ticket-id"
},
"actor": {
"id": "actor-uuid",
"email": "user@example.com",
"name": "John Doe"
},
"organization": {
"id": "org-uuid",
"name": "Acme Corp",
"slug": "acme-corp"
},
"project": {
"id": "project-uuid",
"name": "Web App",
"code": "PROJ",
"slug": "web-app"
},
"changes": {
// Event-specific data
}
}
}
Event-Specific Changes
The changes object contains event-specific data:
ticket_status_changed
"changes": {
"old_status": "open",
"new_status": "in_progress"
}
ticket_assigned
"changes": {
"assignee": {
"id": "assignee-uuid",
"email": "dev@example.com",
"name": "Jane Smith"
}
}
ticket_commented
"changes": {
"comment": {
"id": "comment-uuid",
"content": "I can reproduce this issue...",
"created_at": "2024-01-15T11:00:00Z"
}
}
HTTP Headers
Every webhook request includes the following headers:
| Header | Description |
|---|---|
X-BugToMe-Event |
The event type (e.g., ticket_created) |
X-BugToMe-Delivery-ID |
Unique ID for this delivery (for idempotency) |
X-BugToMe-Signature |
HMAC-SHA256 signature for payload verification |
X-BugToMe-Timestamp |
Unix timestamp of when the webhook was sent |
Content-Type |
application/json |
Verifying Webhook Signatures
Every webhook request is signed using HMAC-SHA256. You should always verify the signature to ensure the request came from BugToMe and wasn't tampered with.
The signature is computed as:
HMAC-SHA256(webhook_secret, timestamp + "." + payload)
The signature header format is: sha256=<hex_signature>
Ruby Example
require 'openssl'
def verify_webhook(request, secret)
signature = request.headers['X-BugToMe-Signature']
timestamp = request.headers['X-BugToMe-Timestamp']
payload = request.body.read
return false unless signature && timestamp
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
'sha256',
secret,
"#{timestamp}.#{payload}"
)
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
Node.js Example
const crypto = require('crypto');
function verifyWebhook(req, secret) {
const signature = req.headers['x-bugtome-signature'];
const timestamp = req.headers['x-bugtome-timestamp'];
const payload = JSON.stringify(req.body);
if (!signature || !timestamp) return false;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
Python Example
import hmac
import hashlib
def verify_webhook(request, secret):
signature = request.headers.get('X-BugToMe-Signature')
timestamp = request.headers.get('X-BugToMe-Timestamp')
payload = request.data.decode('utf-8')
if not signature or not timestamp:
return False
expected = 'sha256=' + hmac.new(
secret.encode('utf-8'),
f'{timestamp}.{payload}'.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Retry Policy
If your endpoint doesn't respond with a 2xx status code, BugToMe will retry the delivery.
The default retry schedule is:
- Attempt 1: Immediate
- Attempt 2: After 1 minute
- Attempt 3: After 5 minutes
- Attempt 4: After 15 minutes
- Attempt 5: After 1 hour
- Attempt 6: After 4 hours (final attempt)
After all retries are exhausted, the delivery is marked as failed.
Best Practices
1. Respond Quickly
Your endpoint should respond within 30 seconds. Process webhooks asynchronously if you need to perform time-consuming operations.
# Good: Queue for async processing
post '/webhooks/bugtome' do
verify_webhook!(request, ENV['WEBHOOK_SECRET'])
WebhookProcessorJob.perform_async(params)
status 200
end
2. Handle Duplicates
Use the X-BugToMe-Delivery-ID header to ensure idempotency.
Store processed delivery IDs and skip duplicates.
3. Verify Signatures
Always verify the webhook signature before processing. Never trust unverified webhook data.
4. Use HTTPS
Only use HTTPS endpoints for webhooks. HTTP endpoints are not supported in production.
5. Handle Failures Gracefully
If your endpoint is temporarily unavailable, BugToMe will retry.
Return a 5xx status code for temporary failures so retries are attempted.
Testing Webhooks
During development, you can use tools like ngrok or webhook.site to receive webhooks on your local machine.
# Start ngrok tunnel
ngrok http 3000
# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/bugtome