Trigger API V2 - Complete Reference
Last Updated: 2025-11-23
📋 Overview
The new Trigger API provides a clean, predictable way to track execution status across 3 different flows:
- Direct Channel API - Email/SMS/WhatsApp send endpoints
- Workflow with Steps - Multi-step workflows (step1 → step2 → step3)
- Workflow with Template Groups - Parallel multi-channel workflows (email + sms + whatsapp)
🏗️ API Architecture
The system uses a two-tier API architecture:
Tier 1: Trigger API (Summary Data)
- Purpose: High-level execution summary and metadata
- Data: Aggregated counts, status, timestamps
- Use case: Dashboard overview, status polling
- Endpoints:
/api/v1/triggers/:id,/api/v1/triggers/:id/direct,/api/v1/triggers/:id/workflow
Tier 2: Execution API (Contact-Level Details)
- Purpose: Detailed contact-level information with pagination
- Data: Individual job records, filter reasons, retry info
- Use case: Debugging, detailed reporting, contact-specific data
- Endpoints (returned in
execution_details_endpoint):- Email:
/api/v1/messages/executions/:id/jobs - SMS:
/api/v1/sms/executions/:id/jobs - WhatsApp:
/api/v1/whatsapp/executions/:id/jobs
- Email:
Important: The Trigger API provides the execution_details_endpoint URL in its response, so frontends always know where to fetch contact-level details.
Example: Using Both Tiers Together
// Step 1: Get trigger summary from Trigger API
const triggerDetails = await fetch('/api/v1/triggers/abc123/direct');
const { execution, jobs, execution_details_endpoint } = await triggerDetails.json();
console.log(`Total: ${jobs.total}, Sent: ${jobs.sent}, Filtered: ${jobs.filtered}`);
// Output: "Total: 100, Sent: 95, Filtered: 5"
// Step 2: If you need contact-level details, use the execution endpoint
if (jobs.filtered > 0) {
const detailsUrl = execution_details_endpoint; // "/api/v1/sms/executions/xyz789/jobs"
const contactDetails = await fetch(`${detailsUrl}?status=filtered&limit=50`);
const { data } = await contactDetails.json();
// Now you have individual contact records with pagination
data.jobs.forEach(job => {
console.log(`${job.phone_number}: ${job.filter_reason}`);
});
}
🎯 Quick Start
Always Use This 2-Step Pattern:
// STEP 1: Get basic trigger info (determines flow type)
const trigger = await GET('/triggers/:id');
// STEP 2: Get detailed info based on flow type
if (trigger.flow_type === 'direct_channel') {
const details = await GET(`/triggers/${id}/direct`);
} else if (trigger.flow_type === 'workflow') {
const details = await GET(`/triggers/${id}/workflow`);
}
📡 API Endpoints
1. GET /triggers/:id - Basic Info (ALWAYS CALL FIRST)
Returns basic trigger information and tells you which detailed endpoint to call.
Request
GET /api/v1/triggers/780cab43-2de4-4ce0-a96b-aeeeed06346c
Headers:
X-API-Key: sk_live_your_api_key
Response
{
"id": "780cab43-2de4-4ce0-a96b-aeeeed06346c",
"flow_type": "direct_channel",
"trigger_type": "sms",
"trigger_source": "direct_api",
"campaign_name": "SMS Send - otp_us",
"status": "completed",
"created_at": "2025-11-23T02:25:28.090Z",
"started_at": "2025-11-23T02:25:44.499Z",
"completed_at": "2025-11-23T02:25:44.630Z",
"scheduled_at": null,
"duration_seconds": 0.131,
"summary": {
"total_contacts": 1,
"sent": 0,
"failed": 0,
"filtered": 1
},
"details_endpoint": "/triggers/780cab43-2de4-4ce0-a96b-aeeeed06346c/direct"
}
Field Descriptions
| Field | Type | Description |
|---|---|---|
flow_type | string | "direct_channel" or "workflow" - Determines which details endpoint to call |
trigger_type | string | "email", "sms", "whatsapp", or "workflow" |
status | string | "pending", "processing", "completed", "failed", "cancelled" |
summary.filtered | number | Count of contacts filtered out (no template, opted out, etc.) |
details_endpoint | string | The URL path to call for detailed information |
2. GET /triggers/:id/direct - Direct Channel Details
For triggers from direct channel send APIs (email/sms/whatsapp).
Request
GET /api/v1/triggers/780cab43-2de4-4ce0-a96b-aeeeed06346c/direct
Headers:
X-API-Key: sk_live_your_api_key
Response - Direct SMS Example
{
"trigger_id": "780cab43-2de4-4ce0-a96b-aeeeed06346c",
"flow_type": "direct_channel",
"channel": "sms",
"execution": {
"id": "85b3b004-5331-4921-b44a-6915f5572a7a",
"status": "completed",
"total_contacts": 1,
"sent_count": 0,
"failed_count": 0,
"filtered_count": 1,
"total_segments": 0,
"estimated_cost": 0.00,
"currency": "USD",
"started_at": "2025-11-23T02:25:44.499Z",
"completed_at": "2025-11-23T02:25:44.630Z"
},
"jobs": {
"total": 1,
"sent": 0,
"failed": 0,
"filtered": 1,
"filter_reasons": [
{
"reason": "No approved SMS template for country: IN",
"count": 1,
"contacts": ["+918489462530"]
}
]
},
"analytics": {
"sent": 0,
"delivered": 0,
"failed": 0,
"clicked": 0,
"opened": 0,
"bounced": 0,
"complained": 0
},
"execution_details_endpoint": "/api/v1/sms/executions/85b3b004-5331-4921-b44a-6915f5572a7a/jobs"
}
Response - Direct Email Example
{
"trigger_id": "abc123...",
"flow_type": "direct_channel",
"channel": "email",
"execution": {
"id": "def456...",
"status": "completed",
"total_contacts": 1000,
"sent_count": 995,
"failed_count": 5,
"filtered_count": 0,
"estimated_cost": 0.00,
"started_at": "2025-11-23T10:00:00.000Z",
"completed_at": "2025-11-23T10:05:30.000Z"
},
"jobs": {
"total": 1000,
"sent": 995,
"failed": 5,
"filtered": 0,
"filter_reasons": []
},
"analytics": {
"sent": 995,
"delivered": 990,
"opened": 450,
"clicked": 120,
"bounced": 5,
"complained": 2,
"unsubscribed": 1
},
"execution_details_endpoint": "/api/v1/messages/executions/def456/jobs"
}
Response - Direct WhatsApp Example
{
"trigger_id": "xyz789...",
"flow_type": "direct_channel",
"channel": "whatsapp",
"execution": {
"id": "ghi012...",
"status": "completed",
"total_contacts": 500,
"sent_count": 495,
"failed_count": 5,
"filtered_count": 0,
"estimated_cost": 14.85,
"currency": "USD",
"started_at": "2025-11-23T08:00:00.000Z",
"completed_at": "2025-11-23T08:10:00.000Z"
},
"jobs": {
"total": 500,
"sent": 495,
"failed": 5,
"filtered": 0,
"filter_reasons": []
},
"analytics": {
"sent": 495,
"delivered": 485,
"read": 420,
"failed": 10,
"replied": 35
},
"execution_details_endpoint": "/api/v1/whatsapp/executions/ghi012/jobs"
}
3. GET /triggers/:id/workflow - Workflow Details
For workflow triggers (both step-based and template group workflows).
Request
GET /api/v1/triggers/123e4567-e89b-12d3-a456-426614174000/workflow
Headers:
X-API-Key: sk_live_your_api_key
Response - Step-Based Workflow
{
"trigger_id": "123e4567-e89b-12d3-a456-426614174000",
"flow_type": "workflow",
"workflow_type": "steps",
"workflow": {
"id": "wf-456...",
"name": "Welcome Series",
"execution_id": "wfex-789..."
},
"steps": [
{
"step_id": "step-1",
"step_order": 1,
"step_name": "Welcome Email",
"channel": "email",
"status": "completed",
"delay_minutes": 0,
"execution": {
"id": "exec-email-1",
"status": "completed",
"total_contacts": 1000,
"sent_count": 995,
"failed_count": 5,
"filtered_count": 0,
"started_at": "2025-11-23T10:00:00.000Z",
"completed_at": "2025-11-23T10:05:00.000Z"
},
"jobs": {
"total": 1000,
"sent": 995,
"failed": 5,
"filtered": 0
},
"analytics": {
"sent": 995,
"delivered": 990,
"opened": 450,
"clicked": 120
}
},
{
"step_id": "step-2",
"step_order": 2,
"step_name": "Day 1 SMS",
"channel": "sms",
"status": "scheduled",
"delay_minutes": 1440,
"scheduled_for": "2025-11-24T10:00:00.000Z",
"execution": null,
"jobs": null,
"analytics": null
},
{
"step_id": "step-3",
"step_order": 3,
"step_name": "Day 3 WhatsApp",
"channel": "whatsapp",
"status": "pending",
"delay_minutes": 4320,
"execution": null,
"jobs": null,
"analytics": null
}
],
"totals": {
"total_contacts": 1000,
"total_sent": 995,
"total_failed": 5,
"total_filtered": 0,
"total_cost": 0.00,
"currency": "USD",
"steps_completed": 1,
"steps_total": 3
}
}
Response - Template Group Workflow (Parallel Channels)
{
"trigger_id": "456def...",
"flow_type": "workflow",
"workflow_type": "template_group",
"workflow": {
"id": "wf-789...",
"name": "Multi-Channel OTP",
"execution_id": "wfex-123...",
"template_group_id": "tg-456..."
},
"channels": {
"email": {
"execution": {
"id": "exec-email-2",
"status": "completed",
"total_contacts": 1000,
"sent_count": 995,
"failed_count": 5,
"filtered_count": 0
},
"jobs": {
"total": 1000,
"sent": 995,
"failed": 5,
"filtered": 0
},
"analytics": {
"sent": 995,
"delivered": 990,
"opened": 450
}
},
"sms": {
"execution": {
"id": "exec-sms-1",
"status": "completed",
"total_contacts": 800,
"sent_count": 795,
"failed_count": 5,
"filtered_count": 0,
"estimated_cost": 23.85
},
"jobs": {
"total": 800,
"sent": 795,
"failed": 5,
"filtered": 0
},
"analytics": {
"sent": 795,
"delivered": 790,
"failed": 5
}
},
"whatsapp": {
"execution": {
"id": "exec-wa-1",
"status": "completed",
"total_contacts": 500,
"sent_count": 495,
"failed_count": 5,
"filtered_count": 0,
"estimated_cost": 14.85
},
"jobs": {
"total": 500,
"sent": 495,
"failed": 5,
"filtered": 0
},
"analytics": {
"sent": 495,
"delivered": 485,
"read": 420,
"replied": 35
}
}
},
"totals": {
"total_contacts": 1000,
"total_sent": 2285,
"total_failed": 15,
"total_filtered": 0,
"total_cost": 38.70,
"currency": "USD",
"channels_completed": 3,
"channels_total": 3
}
}
🔄 Complete Flow Examples
Flow 1: Direct SMS Send
// User sends SMS via direct API
POST /api/v1/messages/sms/send
{
"template_key": "otp_us",
"recipient_type": "contact",
"contact_external_id": "user_123"
}
// Response
{
"success": true,
"trigger_id": "780cab43-2de4-4ce0-a96b-aeeeed06346c",
"job_id": "1"
}
// Frontend checks status
GET /api/v1/triggers/780cab43-2de4-4ce0-a96b-aeeeed06346c
// Response tells you it's a direct channel
{
"flow_type": "direct_channel",
"trigger_type": "sms",
"status": "completed",
"details_endpoint": "/triggers/780cab43-2de4-4ce0-a96b-aeeeed06346c/direct"
}
// Frontend gets detailed stats
GET /api/v1/triggers/780cab43-2de4-4ce0-a96b-aeeeed06346c/direct
// Response has execution + jobs + analytics
{
"channel": "sms",
"execution": { ... },
"jobs": { ... },
"analytics": { ... }
}
Flow 2: Workflow Trigger
// User triggers workflow
POST /api/v1/workflows/trigger
{
"workflow_name": "Welcome Series",
"recipient_type": "contact",
"contact_external_id": "user_123"
}
// Response
{
"success": true,
"trigger_id": "123e4567-e89b-12d3-a456-426614174000"
}
// Frontend checks status
GET /api/v1/triggers/123e4567-e89b-12d3-a456-426614174000
// Response tells you it's a workflow
{
"flow_type": "workflow",
"trigger_type": "workflow",
"status": "processing",
"details_endpoint": "/triggers/123e4567-e89b-12d3-a456-426614174000/workflow"
}
// Frontend gets workflow details
GET /api/v1/triggers/123e4567-e89b-12d3-a456-426614174000/workflow
// Response has all steps/channels
{
"workflow_type": "steps",
"steps": [
{ "step_name": "Welcome Email", "status": "completed", ... },
{ "step_name": "Day 1 SMS", "status": "scheduled", ... }
]
}
🎨 Frontend Implementation Example
interface TriggerBasicInfo {
id: string;
flow_type: 'direct_channel' | 'workflow';
trigger_type: string;
status: string;
summary: {
total_contacts: number;
sent: number;
failed: number;
filtered: number;
};
details_endpoint: string;
}
async function getTriggerDetails(triggerId: string) {
// Step 1: Get basic info
const basic = await fetch(`/api/v1/triggers/${triggerId}`, {
headers: { 'X-API-Key': API_KEY }
}).then(r => r.json()) as TriggerBasicInfo;
// Step 2: Get detailed info based on flow type
if (basic.flow_type === 'direct_channel') {
const details = await fetch(`/api/v1/triggers/${triggerId}/direct`, {
headers: { 'X-API-Key': API_KEY }
}).then(r => r.json());
return { basic, details, type: 'direct' };
} else {
const details = await fetch(`/api/v1/triggers/${triggerId}/workflow`, {
headers: { 'X-API-Key': API_KEY }
}).then(r => r.json());
return { basic, details, type: 'workflow' };
}
}
// Usage
const trigger = await getTriggerDetails('780cab43...');
if (trigger.type === 'direct') {
console.log(`Channel: ${trigger.details.channel}`);
console.log(`Sent: ${trigger.details.execution.sent_count}`);
console.log(`Filtered: ${trigger.details.jobs.filtered}`);
} else {
console.log(`Workflow: ${trigger.details.workflow.name}`);
console.log(`Steps: ${trigger.details.steps.length}`);
}
✅ Benefits of This API Design
- Predictable Structure - Always know what to expect based on
flow_type - Performance - Only fetch what you need
- Type Safety - Clear TypeScript interfaces
- Debugging - Easy to understand what's happening
- Scalability - Easy to add new flow types without breaking existing code
🚨 Common Issues & Solutions
Issue: filtered_count shows contacts but sent is 0
Explanation: Contacts were filtered out before sending (no template for country, opted out, invalid phone/email, etc.)
Check: Look at jobs.filter_reasons array to see why contacts were filtered
Issue: Trigger status is pending but processing completed
Bug: Worker didn't update trigger status. This is a known issue being fixed in SMS/Email/WhatsApp processing workers.
Workaround: Check execution.status instead of trigger.status
📞 Support
For questions or issues, contact the backend team or check the API documentation at /api/docs