Skip to main content

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โ€‹

  1. Navigate to Settings โ†’ Webhooks in your dashboard
  2. Click Add Endpoint
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. 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 event
  • type - The type of event (e.g., session.created)
  • created - ISO 8601 timestamp when the event occurred
  • data - Event-specific data object
  • object - Always "event"
  • api_version - API version used for the event
  • account_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:

  1. Go to Settings โ†’ Webhooks
  2. Click on your endpoint
  3. Click Send Test Event
  4. 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โ€‹

  1. Always verify signatures - Never process webhooks without verification
  2. Use HTTPS - Webhook endpoints must use HTTPS in production
  3. Implement rate limiting - Protect against webhook floods
  4. Log events - Keep audit trail of all webhook activity
  5. Monitor failures - Set up alerts for webhook failures
  6. Secure secrets - Store signing secrets in environment variables
  7. Validate data - Don't trust webhook data without validation

๐Ÿ“Š Monitoringโ€‹

Webhook Logsโ€‹

View webhook delivery logs in the dashboard:

  1. Go to Settings โ†’ Webhooks
  2. Click on your endpoint
  3. 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);
}
});