Business events
Start automations from your own system.
The business builds automations in the dashboard - "when an order is paid, send the thank-you template, tag the contact, create a follow-up in three days" - and your system fires the trigger. You send a named event; the platform runs whatever workflows listen for it.
Sending an event
curl -X POST "https://communication-api.artofluminaire.com/api/v1/events" \
-H "Authorization: Bearer omni_live_YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ORDER-10025-PAID" \
-d '{
"event": "order.paid",
"identity": {
"channel": "whatsapp",
"external_id": "919876543210"
},
"data": {
"order_id": "10025",
"amount": 4850,
"currency": "INR"
},
"dedupe_key": "ORDER-10025-PAID"
}'// Node.js 18+ (built-in fetch), saved as request.mjs for top-level await. Replace the key with your own.
const response = await fetch("https://communication-api.artofluminaire.com/api/v1/events", {
method: "POST",
headers: {
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "ORDER-10025-PAID",
},
body: JSON.stringify({
"event": "order.paid",
"identity": {
"channel": "whatsapp",
"external_id": "919876543210"
},
"data": {
"order_id": "10025",
"amount": 4850,
"currency": "INR"
},
"dedupe_key": "ORDER-10025-PAID"
}),
});
const payload = await response.json();
if (!response.ok) {
// Every error is { error: { code, message, details, request_id } }.
throw new Error(`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`);
}
console.log(payload.data);# pip install requests
import requests
response = requests.post(
"https://communication-api.artofluminaire.com/api/v1/events",
headers={
"Authorization": "Bearer omni_live_YOUR_API_KEY",
"Accept": "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "ORDER-10025-PAID",
},
json={
"event": "order.paid",
"identity": {
"channel": "whatsapp",
"external_id": "919876543210",
},
"data": {
"order_id": "10025",
"amount": 4850,
"currency": "INR",
},
"dedupe_key": "ORDER-10025-PAID",
},
timeout=30,
)
payload = response.json()
if not response.ok:
# Every error is {"error": {"code", "message", "details", "request_id"}}.
error = payload["error"]
raise RuntimeError(f"{error['code']}: {error['message']} (request {error['request_id']})")
print(payload["data"])<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client(['http_errors' => false, 'timeout' => 30]);
$response = $client->post('https://communication-api.artofluminaire.com/api/v1/events', [
'headers' => [
'Authorization' => 'Bearer omni_live_YOUR_API_KEY',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Idempotency-Key' => 'ORDER-10025-PAID',
],
'json' => [
'event' => 'order.paid',
'identity' => [
'channel' => 'whatsapp',
'external_id' => '919876543210',
],
'data' => [
'order_id' => '10025',
'amount' => 4850,
'currency' => 'INR',
],
'dedupe_key' => 'ORDER-10025-PAID',
],
]);
$payload = json_decode((string) $response->getBody(), true);
if ($response->getStatusCode() >= 400) {
// Every error is ['error' => ['code', 'message', 'details', 'request_id']].
throw new RuntimeException(sprintf('%s: %s (request %s)', $payload['error']['code'], $payload['error']['message'], $payload['error']['request_id']));
}
echo json_encode($payload['data'], JSON_PRETTY_PRINT), PHP_EOL;// Java 17+, no dependencies (java.net.http). Parse the JSON with your usual library.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://communication-api.artofluminaire.com/api/v1/events"))
.header("Authorization", "Bearer omni_live_YOUR_API_KEY")
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "ORDER-10025-PAID")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"event": "order.paid",
"identity": {
"channel": "whatsapp",
"external_id": "919876543210"
},
"data": {
"order_id": "10025",
"amount": 4850,
"currency": "INR"
},
"dedupe_key": "ORDER-10025-PAID"
}
"""))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new RuntimeException("Request failed: " + response.body());
}
System.out.println(response.body());
}
}// .NET 7+ (C# 11 raw string literals). Parse the JSON with System.Text.Json.
using System;
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://communication-api.artofluminaire.com/api/v1/events");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer omni_live_YOUR_API_KEY");
request.Headers.TryAddWithoutValidation("Accept", "application/json");
request.Headers.TryAddWithoutValidation("Idempotency-Key", "ORDER-10025-PAID");
request.Content = new StringContent("""
{
"event": "order.paid",
"identity": {
"channel": "whatsapp",
"external_id": "919876543210"
},
"data": {
"order_id": "10025",
"amount": 4850,
"currency": "INR"
},
"dedupe_key": "ORDER-10025-PAID"
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Every error is {"error": {"code", "message", "details", "request_id"}}.
throw new Exception($"Request failed: {payload}");
}
Console.WriteLine(payload);// Go 1.20+, standard library only. Decode the JSON with encoding/json.
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"event": "order.paid",
"identity": {
"channel": "whatsapp",
"external_id": "919876543210"
},
"data": {
"order_id": "10025",
"amount": 4850,
"currency": "INR"
},
"dedupe_key": "ORDER-10025-PAID"
}`)
req, err := http.NewRequest("POST", "https://communication-api.artofluminaire.com/api/v1/events", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer omni_live_YOUR_API_KEY")
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "ORDER-10025-PAID")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
// Every error is {"error": {"code", "message", "details", "request_id"}}.
panic(fmt.Sprintf("request failed: %s", payload))
}
fmt.Println(string(payload))
}Try it - send it and see the response
eventis a name you and the business agree on: lowercase, dots and underscores (order.paid,booking.confirmed,cart.abandoned). The dashboard's trigger picker lists the names it has seen, so pick one and keep it.- Identify the customer by
contact_idif you have it, or by anidentity- the channel and the handle (+919876543210onwhatsapp). The handle must already belong to a contact; an unknown one answers404, so create the contact first (Contacts) or send them a message, which creates it. Without either the event still fires, but the workflow has nobody to message. datais yours: every workflow condition and message variable can read it as{{data.order_id}},{{data.amount}}- nested keys too ({{data.address.city}}). The event name is{{event}}.dedupe_keyprotects the automation, not the request: two events with the same key for the same contact run the workflow once, even if they arrive minutes apart from two of your servers. Use the thing that must not happen twice - the order id, the booking id.
The answer is 202 with the event id and how many workflows were started. Zero is not an error; it means nothing listens for that name yet.
What runs
Only published workflows with an External API event trigger matching the name - Automations shows how the business builds one and what it can do. The business sees every run step by step in the dashboard - what fired, which conditions held, what each action did - and your request_id is on the run, so a "why did my customer get this?" question is answerable.
Idempotency
POST /events is a write, so the Idempotency-Key header is required like every other mutation (Idempotency). The key deduplicates the HTTP call for 24 hours; dedupe_key deduplicates the business event for as long as the subject exists. Use both.
https://communication-api.artofluminaire.com
Every response carries X-Request-ID; quote it when you write to support.