Skip to main content

Sending Events

This page covers everything you need to trigger notifications from your application — the endpoint, request format, channel routing, code examples, and idempotency.


Prerequisites

Before triggering events, make sure you have:

  1. A tenant API key — provided by your platform operator. Never expose this in client-side code.
  2. At least one active provider — configured in Admin UI → Providers.
  3. At least one active template — matching the event type you plan to trigger.

If any of these are missing, events are accepted but nothing is delivered.


Step 1 — Get your credentials

CredentialPurposeWhere to use
Tenant API KeyAuthenticates every event triggerx-api-key header
Webhook Secret (optional)HMAC-SHA256 payload signingCompute and send as X-Orqestra-Signature
danger

Your Tenant API Key is a privileged secret. Never expose it in client-side code, public repositories, or browser network requests.


Step 2 — Configure a delivery provider in the Admin UI

Go to Admin UI → Providers and add credentials for the channels you intend to use:

ChannelSupported ProvidersWhat you need
EmailResend, SendGridAPI key from the provider dashboard
SMSTwilio, Africa's TalkingAccount SID + Auth Token (Twilio) or API key
Real-time PushBuilt-inNo external account needed

Step 3 — Create a template

Go to Admin UI → Templates and create a template:

  1. Set the event type you will trigger (e.g. order.confirmed)
  2. Choose the channel (Email, SMS, or Push)
  3. Write the content using Handlebars variables — any field in your variables object is available as {{fieldName}}
  4. For Email, use the MJML editor for responsive HTML
  5. Activate the template — inactive templates are ignored at dispatch time

:::tip Global templates Use one of the built-in global.* event types (global.info, global.success, global.warning, global.alert, global.error) to test without creating custom templates. These resolve against platform-wide templates automatically. :::


The endpoint

POST /api/v1/events/trigger

Headers

HeaderRequiredDescription
Content-TypeYesMust be application/json
x-api-keyYesYour tenant API key
X-Orqestra-SignatureConditionalRequired if your tenant has a webhook secret configured. See Webhook Security.
X-Idempotency-KeyNoCustom deduplication key. See Idempotency.
X-Trace-IdNoTrace ID for distributed tracing and debugging

Request body

{
"eventType": "order.confirmed",
"payload": {
"userId": "uuid-of-the-recipient",
"recipientEmail": "customer@example.com",
"recipientPhone": "+254712345678"
},
"variables": {
"orderNumber": "ORD-2026-1234",
"amount": "$49.99",
"customerName": "Alice"
}
}

payload carries routing identity — who to notify and on which address. variables carries template data — the values that get interpolated into your message content at render time.

FieldTypeRequiredDescription
eventTypestringYesThe event identifier matching a configured template
eventIdstringNoExplicit event ID (auto-generated if omitted)
channelsstring[]NoPer-trigger channel override. Restricts this dispatch to listed channels only. Valid: "EMAIL", "SMS", "PUSH".
payload.userIdstringYesThe recipient's user ID in your system
payload.recipientEmailstringNoRequired for email channel
payload.recipientPhonestringNoRequired for SMS channel (E.164 format)
variablesobjectNoArbitrary key/value pairs available as {{fieldName}} in your templates. Shape depends entirely on what your template expects — there are no reserved keys.

Successful response

{
"success": true,
"message": "Event order.confirmed dispatched securely for YourTenantName"
}

Channel routing

By default, every active template matching the eventType is dispatched (Email, SMS, Push simultaneously). Two controls narrow this:

Tenant channel policy (set by platform operator)

Each tenant has a delivery_channels whitelist:

ValueBehaviour
[] emptyAll channels with matching active templates dispatch
["EMAIL"]Only email templates dispatch
["EMAIL", "PUSH"]Email and push dispatch; SMS skipped

Per-trigger override (set by your application)

{
"eventType": "global.alert",
"channels": ["EMAIL"],
"payload": { "userId": "...", "recipientEmail": "..." },
"variables": { "title": "...", "message": "..." }
}

The per-trigger channels filter applies after the tenant policy. It can narrow further but cannot expand beyond what the tenant policy permits.


Global templates

Five built-in event types work without custom templates:

eventTypeUse case
global.infoInformational updates
global.successPositive outcomes, approvals
global.warningReminders, upcoming deadlines
global.alertImportant issues, urgent state
global.errorSystem or workflow failures

Recommended shape for global events:

{
"eventType": "global.success",
"payload": {
"userId": "uuid",
"recipientEmail": "user@example.com"
},
"variables": {
"title": "Application Approved",
"message": "Your loan application has been approved."
}
}

Always include title and message in variables — these are the minimum fields expected by the built-in global templates.


Code examples

cURL

curl -X POST https://your-orqestra-host/api/v1/events/trigger \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_TENANT_API_KEY" \
-d '{
"eventType": "order.confirmed",
"payload": {
"userId": "b5dae3e8-2d5d-6f3c-cd3c-3c4d5e6f7a8b",
"recipientEmail": "customer@example.com"
},
"variables": {
"orderNumber": "ORD-2026-1234",
"amount": "$49.99",
"customerName": "Alice"
}
}'

Node.js / TypeScript

const response = await fetch('https://your-orqestra-host/api/v1/events/trigger', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.ORQESTRA_API_KEY!,
},
body: JSON.stringify({
eventType: 'order.confirmed',
payload: {
userId: user.id,
recipientEmail: user.email,
},
variables: {
orderNumber: order.number,
amount: order.formattedTotal,
customerName: user.firstName,
},
}),
});

const result = await response.json();

Python

import requests, os

response = requests.post(
"https://your-orqestra-host/api/v1/events/trigger",
headers={
"Content-Type": "application/json",
"x-api-key": os.environ["ORQESTRA_API_KEY"],
},
json={
"eventType": "order.confirmed",
"payload": {
"userId": user_id,
"recipientEmail": user_email,
},
"variables": {
"orderNumber": "ORD-2026-1234",
"amount": "$49.99",
"customerName": customer_name,
},
},
)

Go

body, _ := json.Marshal(map[string]any{
"eventType": "order.confirmed",
"payload": map[string]any{
"userId": userID,
"recipientEmail": userEmail,
},
"variables": map[string]any{
"orderNumber": "ORD-2026-1234",
"amount": "$49.99",
"customerName": customerName,
},
})

req, _ := http.NewRequest("POST", orqestraURL+"/api/v1/events/trigger", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", os.Getenv("ORQESTRA_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Idempotency

The engine automatically deduplicates events using a composite key of tenantId + eventType + payload. If your backend retries a failed request, the same event won't be processed twice.

To override deduplication (e.g. for intentional reminder resends), provide a unique key:

curl -X POST https://your-orqestra-host/api/v1/events/trigger \
-H "x-api-key: YOUR_KEY" \
-H "X-Idempotency-Key: reminder-2026-04-22-user-001" \
-H "Content-Type: application/json" \
-d '{ "eventType": "global.warning", "payload": { "userId": "...", "recipientEmail": "..." }, "variables": { "title": "...", "message": "..." } }'

Idempotency entries expire after 24 hours.


Rate limits

Limit TypeDefaultBehaviour
Per-minuteConfigured per tenantSliding window counter
Daily capConfigured per tenantResets at midnight UTC

When exceeded:

{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded for tenant YourApp. Limit: 100/min",
"retryAfter": 45
}

The response includes a Retry-After header with seconds to wait.


Error responses

StatusMeaningCommon cause
200AcceptedSuccess
401Authentication failedInvalid/missing x-api-key, invalid HMAC signature, inactive tenant
422Validation errorMissing eventType, payload, or payload.userId
429Rate limit exceededToo many requests — respect Retry-After
500Internal errorKafka unavailable, database error

Next steps