Skip to main content

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:

  1. Direct Channel API - Email/SMS/WhatsApp send endpoints
  2. Workflow with Steps - Multi-step workflows (step1 → step2 → step3)
  3. 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

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

FieldTypeDescription
flow_typestring"direct_channel" or "workflow" - Determines which details endpoint to call
trigger_typestring"email", "sms", "whatsapp", or "workflow"
statusstring"pending", "processing", "completed", "failed", "cancelled"
summary.filterednumberCount of contacts filtered out (no template, opted out, etc.)
details_endpointstringThe 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

  1. Predictable Structure - Always know what to expect based on flow_type
  2. Performance - Only fetch what you need
  3. Type Safety - Clear TypeScript interfaces
  4. Debugging - Easy to understand what's happening
  5. 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