Webhooks Reference
Webhooks allow you to receive real-time HTTP notifications when events occur in your TrendGate account. This enables you to build reactive applications and automate workflows.
๐ Overviewโ
Webhooks are HTTP callbacks that are triggered by specific events. When an event occurs, TrendGate sends an HTTP POST request to your configured endpoint with details about the event.
Common Use Casesโ
- Sync data - Keep your database in sync with TrendGate
- Send notifications - Email or SMS users about important events
- Update external systems - Integrate with CRM, accounting, or other tools
- Automate workflows - Trigger actions based on platform events
- Analytics - Track events for reporting and insights
๐ง Setupโ
1. Create Webhook Endpointโ
First, create an endpoint in your application to receive webhooks:
// Node.js/Express example
app.post("/webhooks/trendgate", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["trendgate-signature"];
const timestamp = req.headers["trendgate-timestamp"];
const body = req.body;
// Verify webhook signature
if (!verifyWebhookSignature(body, signature, timestamp)) {
return res.status(401).send("Unauthorized");
}
// Parse event
const event = JSON.parse(body);
// Handle event
switch (event.type) {
case "session.created":
handleSessionCreated(event.data);
break;
case "booking.confirmed":
handleBookingConfirmed(event.data);
break;
// ... handle other events
}
// Return 200 OK to acknowledge receipt
res.status(200).send("OK");
});
2. Configure Webhook in Dashboardโ
- Navigate to Settings โ Webhooks in your dashboard
- Click Add Endpoint
- Enter your endpoint URL
- Select events to subscribe to
- Copy the signing secret for verification
3. Verify Webhook Signaturesโ
Always verify webhook signatures to ensure requests are from TrendGate:
const crypto = require("crypto");
function verifyWebhookSignature(payload, signature, timestamp) {
const secret = process.env.TRENDGATE_WEBHOOK_SECRET;
// Prevent replay attacks
const currentTime = Math.floor(Date.now() / 1000);
if (currentTime - parseInt(timestamp) > 300) {
// 5 minutes
return false;
}
// Calculate expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto.createHmac("sha256", secret).update(signedPayload).digest("hex");
// Compare signatures
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
}
๐ Event Formatโ
All webhook events follow this structure:
{
"id": "evt_1234567890",
"type": "session.created",
"created": "2024-01-20T10:30:00Z",
"data": {
// Event-specific data
},
"object": "event",
"api_version": "2024-01",
"account_id": "acc_1234567890"
}
Event Propertiesโ
id- Unique identifier for the eventtype- The type of event (e.g.,session.created)created- ISO 8601 timestamp when the event occurreddata- Event-specific data objectobject- Always"event"api_version- API version used for the eventaccount_id- Your account ID
๐ Available Eventsโ
Session Eventsโ
session.createdโ
Triggered when a new session is created.
{
"type": "session.created",
"data": {
"session": {
"id": "sess_123",
"instructor_id": "inst_456",
"member_id": "mem_789",
"date": "2024-02-01T10:00:00Z",
"duration_minutes": 30,
"status": "scheduled",
"pool": {
"id": "pool_123",
"name": "Community Center Pool"
}
}
}
}
session.updatedโ
Triggered when a session is modified.
{
"type": "session.updated",
"data": {
"session": {
// Full session object with updated fields
},
"previous_attributes": {
"date": "2024-02-01T10:00:00Z",
"duration_minutes": 30
}
}
}
session.cancelledโ
Triggered when a session is cancelled.
{
"type": "session.cancelled",
"data": {
"session": {
"id": "sess_123",
"status": "cancelled",
"cancelled_at": "2024-01-20T10:30:00Z",
"cancel_reason": "Weather conditions"
}
}
}
session.completedโ
Triggered when a session is marked as completed.
{
"type": "session.completed",
"data": {
"session": {
"id": "sess_123",
"status": "completed",
"completed_at": "2024-02-01T10:45:00Z",
"actual_duration_minutes": 35,
"notes": "Great progress on backstroke!"
}
}
}
Booking Eventsโ
booking.createdโ
Triggered when a new booking is created.
{
"type": "booking.created",
"data": {
"booking": {
"id": "book_123",
"account_id": "acc_456",
"instructor_id": "inst_789",
"member_ids": ["mem_111", "mem_222"],
"session_count": 4,
"total_amount": 20000, // $200.00 in cents
"status": "pending",
"created_at": "2024-01-20T10:30:00Z"
}
}
}
booking.confirmedโ
Triggered when a booking is confirmed (usually after payment).
{
"type": "booking.confirmed",
"data": {
"booking": {
"id": "book_123",
"status": "confirmed",
"confirmed_at": "2024-01-20T10:31:00Z",
"payment": {
"id": "pay_456",
"amount": 20000,
"currency": "USD",
"status": "succeeded"
}
}
}
}
booking.cancelledโ
Triggered when a booking is cancelled.
{
"type": "booking.cancelled",
"data": {
"booking": {
"id": "book_123",
"status": "cancelled",
"cancelled_at": "2024-01-20T15:00:00Z",
"refund": {
"amount": 20000,
"status": "processed"
}
}
}
}
Payment Eventsโ
payment.succeededโ
Triggered when a payment is successfully processed.
{
"type": "payment.succeeded",
"data": {
"payment": {
"id": "pay_123",
"booking_id": "book_456",
"amount": 5000, // $50.00 in cents
"currency": "USD",
"payment_method": {
"type": "card",
"last4": "4242"
},
"created_at": "2024-01-20T10:30:00Z"
}
}
}
payment.failedโ
Triggered when a payment fails.
{
"type": "payment.failed",
"data": {
"payment": {
"id": "pay_123",
"booking_id": "book_456",
"amount": 5000,
"currency": "USD",
"failure_reason": "insufficient_funds",
"failure_message": "Your card has insufficient funds."
}
}
}
payment.refundedโ
Triggered when a payment is refunded.
{
"type": "payment.refunded",
"data": {
"refund": {
"id": "ref_123",
"payment_id": "pay_456",
"amount": 5000,
"reason": "requested_by_customer",
"status": "succeeded",
"created_at": "2024-01-20T15:00:00Z"
}
}
}
Instructor Eventsโ
instructor.approvedโ
Triggered when an instructor application is approved.
{
"type": "instructor.approved",
"data": {
"instructor": {
"id": "inst_123",
"user_id": "user_456",
"approved_at": "2024-01-20T10:30:00Z",
"certifications": [
{
"type": "red_cross_wsi",
"verified": true
}
]
}
}
}
instructor.suspendedโ
Triggered when an instructor is suspended.
{
"type": "instructor.suspended",
"data": {
"instructor": {
"id": "inst_123",
"suspended_at": "2024-01-20T10:30:00Z",
"reason": "policy_violation",
"suspended_until": "2024-02-20T10:30:00Z"
}
}
}
Member Eventsโ
member.createdโ
Triggered when a new member is added to an account.
{
"type": "member.created",
"data": {
"member": {
"id": "mem_123",
"account_id": "acc_456",
"name": "Sarah Johnson",
"date_of_birth": "2015-03-15",
"swimming_level": "beginner",
"created_at": "2024-01-20T10:30:00Z"
}
}
}
member.progress_updatedโ
Triggered when a member's progress is updated.
{
"type": "member.progress_updated",
"data": {
"progress": {
"id": "prog_123",
"member_id": "mem_456",
"session_id": "sess_789",
"skills_learned": ["floating", "breathing"],
"notes": "Excellent progress today!",
"swimming_level": {
"previous": "beginner",
"current": "intermediate"
}
}
}
}
๐จ Error Handlingโ
Retry Logicโ
TrendGate automatically retries failed webhook deliveries with exponential backoff:
- 1st retry: 5 seconds
- 2nd retry: 30 seconds
- 3rd retry: 2 minutes
- 4th retry: 10 minutes
- 5th retry: 30 minutes
- 6th retry: 2 hours
- 7th retry: 6 hours
- 8th retry: 24 hours
After 8 failed attempts, the webhook is marked as failed.
Expected Responseโ
Your endpoint should return a 2xx status code (200-299) to acknowledge receipt. Any other status code is considered a failure.
// Success
res.status(200).send("OK");
// Temporary failure (will retry)
res.status(500).send("Server Error");
// Permanent failure (will not retry)
res.status(400).send("Bad Request");
Handling Duplicate Eventsโ
Events may be delivered more than once. Implement idempotent handling:
const processedEvents = new Set();
async function handleWebhook(event) {
// Check if already processed
if (processedEvents.has(event.id)) {
return;
}
// Process event
await processEvent(event);
// Mark as processed
processedEvents.add(event.id);
// Store in database for persistence
await db.processedEvents.create({
event_id: event.id,
processed_at: new Date(),
});
}
๐งช Testing Webhooksโ
Test Eventsโ
Send test events from the dashboard:
- Go to Settings โ Webhooks
- Click on your endpoint
- Click Send Test Event
- Select event type and click Send
Local Developmentโ
Use ngrok or similar to test webhooks locally:
# Install ngrok
npm install -g ngrok
# Start your local server
npm run dev
# Expose local server
ngrok http 3000
# Use the ngrok URL for webhook configuration
# https://abc123.ngrok.io/webhooks/trendgate
Webhook CLIโ
Use our CLI tool for testing:
# Install CLI
npm install -g @trendgate/cli
# Listen for webhooks
trendgate webhooks listen
# Trigger test event
trendgate webhooks trigger session.created
# View recent events
trendgate webhooks events --limit 10
๐ Security Best Practicesโ
- Always verify signatures - Never process webhooks without verification
- Use HTTPS - Webhook endpoints must use HTTPS in production
- Implement rate limiting - Protect against webhook floods
- Log events - Keep audit trail of all webhook activity
- Monitor failures - Set up alerts for webhook failures
- Secure secrets - Store signing secrets in environment variables
- Validate data - Don't trust webhook data without validation
๐ Monitoringโ
Webhook Logsโ
View webhook delivery logs in the dashboard:
- Go to Settings โ Webhooks
- Click on your endpoint
- View Recent Deliveries
Each log entry shows:
- Event type and ID
- Delivery status
- Response code
- Response time
- Request/response bodies
Metricsโ
Monitor webhook health:
// Track metrics
const metrics = {
received: 0,
processed: 0,
failed: 0,
duration: [],
};
app.post("/webhooks", async (req, res) => {
const start = Date.now();
metrics.received++;
try {
await processWebhook(req.body);
metrics.processed++;
res.status(200).send("OK");
} catch (error) {
metrics.failed++;
res.status(500).send("Error");
} finally {
metrics.duration.push(Date.now() - start);
}
});