curl --request GET \
--url https://api.flowiq.live/broadcast-analytics \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.flowiq.live/broadcast-analytics"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.flowiq.live/broadcast-analytics', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.flowiq.live/broadcast-analytics",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.flowiq.live/broadcast-analytics"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.flowiq.live/broadcast-analytics")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.flowiq.live/broadcast-analytics")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"broadcastDetails": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "march_promo_v2 - 2026-03-15 10:00:00",
"templateName": "march_promo_v2",
"createdAt": "2026-03-15T10:00:00+02:00",
"totalRecipients": 1500,
"mediaUrl": null,
"templateParameters": {
"body_parameters": {
"param1": "{{first_name}}"
},
"button_parameters": {
"param1": "x7k2pq"
}
}
},
"deliveryStats": {
"sent": 1500,
"delivered": 1420,
"read": 890,
"failed": 80,
"deliveryRate": 94.67,
"readRate": 62.68
},
"conversionFunnel": {
"broadcastSent": 1500,
"delivered": 1420,
"read": 890,
"buttonClicked": 63,
"redirectLinkClicks": 41,
"optOuts": 4
},
"buttonAnalytics": {
"quickReplyButton": {
"buttonText": "Shop Now",
"buttonType": "QUICK_REPLY",
"clicks": 29,
"conversionRate": 2.04
},
"allButtons": [
{
"buttonText": "Shop Now",
"clicks": 29,
"conversionRate": 2.04
},
{
"buttonText": "View More",
"clicks": 23,
"conversionRate": 1.62
},
{
"buttonText": "Not Interested",
"clicks": 11,
"conversionRate": 0.77
}
],
"totalButtonClicks": 63
},
"linkAnalytics": {
"linkType": "REDIRECT_LINK",
"isTrackable": true,
"redirectCode": "x7k2pq",
"templateUrl": "https://linklnk.io/{{1}}",
"redirectUrl": "https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo",
"displayUrl": "https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo",
"totalClicks": 41,
"clickThroughRate": 2.89,
"note": "Redirects to: https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo"
},
"customerResponses": {
"totalResponses": 88,
"uniqueResponders": 71,
"responseRate": 6.2,
"breakdown": {
"quickReplyClicks": 63,
"optOuts": 4,
"other": 21
}
},
"revenue": {
"totalRevenue": 19302,
"currency": "ZAR",
"orderCount": 11,
"avgOrderValue": 1754.73,
"utmCampaign": "31marstockup",
"utmCampaigns": [
{
"campaign": "31marstockup",
"orderCount": 6,
"revenue": 10880,
"avgOrderValue": 1813.33
},
{
"campaign": "31marstockup_vm",
"orderCount": 5,
"revenue": 8422,
"avgOrderValue": 1684.4
}
],
"orders": [
{
"orderName": "#28556",
"totalPrice": 1650,
"currency": "ZAR",
"orderDate": "2026-04-03T07:35:39+02:00",
"financialStatus": "paid",
"utmCampaign": "31marstockup_vm"
}
]
},
"version": "v8-short-text-or-short-code-lookup"
}
}Broadcast Analytics
Retrieve broadcast analytics for your organization. Supports two modes:
- Individual broadcast — pass
broadcast_idto get detailed analytics for a single broadcast, including per-button click tracking, opt-out detection, conversion funnel, and revenue attribution. - Overall analytics — pass
start_dateandend_dateto get aggregated analytics across all broadcasts in a date range.
Requirements
- Valid API key with
fiq_prefix - For individual mode: a valid broadcast UUID
- For overall mode: both
start_dateandend_datequery parameters
curl --request GET \
--url https://api.flowiq.live/broadcast-analytics \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.flowiq.live/broadcast-analytics"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.flowiq.live/broadcast-analytics', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.flowiq.live/broadcast-analytics",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.flowiq.live/broadcast-analytics"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.flowiq.live/broadcast-analytics")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.flowiq.live/broadcast-analytics")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"broadcastDetails": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "march_promo_v2 - 2026-03-15 10:00:00",
"templateName": "march_promo_v2",
"createdAt": "2026-03-15T10:00:00+02:00",
"totalRecipients": 1500,
"mediaUrl": null,
"templateParameters": {
"body_parameters": {
"param1": "{{first_name}}"
},
"button_parameters": {
"param1": "x7k2pq"
}
}
},
"deliveryStats": {
"sent": 1500,
"delivered": 1420,
"read": 890,
"failed": 80,
"deliveryRate": 94.67,
"readRate": 62.68
},
"conversionFunnel": {
"broadcastSent": 1500,
"delivered": 1420,
"read": 890,
"buttonClicked": 63,
"redirectLinkClicks": 41,
"optOuts": 4
},
"buttonAnalytics": {
"quickReplyButton": {
"buttonText": "Shop Now",
"buttonType": "QUICK_REPLY",
"clicks": 29,
"conversionRate": 2.04
},
"allButtons": [
{
"buttonText": "Shop Now",
"clicks": 29,
"conversionRate": 2.04
},
{
"buttonText": "View More",
"clicks": 23,
"conversionRate": 1.62
},
{
"buttonText": "Not Interested",
"clicks": 11,
"conversionRate": 0.77
}
],
"totalButtonClicks": 63
},
"linkAnalytics": {
"linkType": "REDIRECT_LINK",
"isTrackable": true,
"redirectCode": "x7k2pq",
"templateUrl": "https://linklnk.io/{{1}}",
"redirectUrl": "https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo",
"displayUrl": "https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo",
"totalClicks": 41,
"clickThroughRate": 2.89,
"note": "Redirects to: https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo"
},
"customerResponses": {
"totalResponses": 88,
"uniqueResponders": 71,
"responseRate": 6.2,
"breakdown": {
"quickReplyClicks": 63,
"optOuts": 4,
"other": 21
}
},
"revenue": {
"totalRevenue": 19302,
"currency": "ZAR",
"orderCount": 11,
"avgOrderValue": 1754.73,
"utmCampaign": "31marstockup",
"utmCampaigns": [
{
"campaign": "31marstockup",
"orderCount": 6,
"revenue": 10880,
"avgOrderValue": 1813.33
},
{
"campaign": "31marstockup_vm",
"orderCount": 5,
"revenue": 8422,
"avgOrderValue": 1684.4
}
],
"orders": [
{
"orderName": "#28556",
"totalPrice": 1650,
"currency": "ZAR",
"orderDate": "2026-04-03T07:35:39+02:00",
"financialStatus": "paid",
"utmCampaign": "31marstockup_vm"
}
]
},
"version": "v8-short-text-or-short-code-lookup"
}
}Two Modes
The endpoint supports two query modes based on which parameters you provide:| Mode | Parameters | Description |
|---|---|---|
| Individual | broadcast_id | Detailed analytics for a single broadcast |
| Overall | start_date + end_date | Aggregated analytics across all broadcasts in a date range |
Individual Broadcast Analytics
Pass abroadcast_id to get detailed metrics for a single broadcast, including delivery stats, per-button click tracking, opt-out detection, and Shopify revenue attribution.
curl "https://api.flowiq.live/broadcast-analytics?broadcast_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer fiq_YOUR_API_KEY"
Response Fields
All fields are insidedata.
| Field | Description |
|---|---|
broadcastDetails | Broadcast metadata: id, name, templateName, createdAt, totalRecipients, mediaUrl, and the templateParameters it was sent with |
deliveryStats | sent, delivered, read, failed, plus deliveryRate (delivered ÷ sent) and readRate (read ÷ delivered), as percentages |
conversionFunnel | broadcastSent → delivered → read → buttonClicked, plus redirectLinkClicks and optOuts |
buttonAnalytics | Quick-reply button tracking: allButtons, totalButtonClicks, and quickReplyButton (the first button, kept for backward compatibility) |
linkAnalytics | The template’s URL button: link type, the tracked short link and where it redirects, total clicks and click-through rate |
customerResponses | Replies from the broadcast’s recipients within 48 hours: totalResponses, uniqueResponders, responseRate (responses ÷ delivered), and a breakdown into quickReplyClicks, optOuts and other (everything else) |
revenue | Shopify revenue attributed to this broadcast via UTM tracking (details) |
version | Version tag of the analytics calculation |
{
"success": true,
"data": {
"broadcastDetails": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "march_promo_v2 - 2026-03-15 10:00:00",
"templateName": "march_promo_v2",
"createdAt": "2026-03-15T10:00:00+02:00",
"totalRecipients": 1500,
"mediaUrl": null,
"templateParameters": {
"body_parameters": { "param1": "{{first_name}}" },
"button_parameters": { "param1": "x7k2pq" }
}
},
"deliveryStats": {
"sent": 1500,
"delivered": 1420,
"read": 890,
"failed": 80,
"deliveryRate": 94.67,
"readRate": 62.68
},
"conversionFunnel": {
"broadcastSent": 1500,
"delivered": 1420,
"read": 890,
"buttonClicked": 63,
"redirectLinkClicks": 41,
"optOuts": 4
},
"buttonAnalytics": {
"quickReplyButton": { "buttonText": "Shop Now", "buttonType": "QUICK_REPLY", "clicks": 29, "conversionRate": 2.04 },
"allButtons": [
{ "buttonText": "Shop Now", "clicks": 29, "conversionRate": 2.04 },
{ "buttonText": "View More", "clicks": 23, "conversionRate": 1.62 },
{ "buttonText": "Not Interested", "clicks": 11, "conversionRate": 0.77 }
],
"totalButtonClicks": 63
},
"linkAnalytics": {
"linkType": "REDIRECT_LINK",
"isTrackable": true,
"redirectCode": "x7k2pq",
"templateUrl": "https://linklnk.io/{{1}}",
"redirectUrl": "https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo",
"displayUrl": "https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo",
"totalClicks": 41,
"clickThroughRate": 2.89,
"note": "Redirects to: https://shop.example.com/sale?utm_source=whatsapp&utm_campaign=march_promo"
},
"customerResponses": {
"totalResponses": 88,
"uniqueResponders": 71,
"responseRate": 6.2,
"breakdown": { "quickReplyClicks": 63, "optOuts": 4, "other": 21 }
},
"revenue": { "totalRevenue": 19302.0, "currency": "ZAR", "orderCount": 11, "avgOrderValue": 1754.73, "utmCampaign": "march_promo", "utmCampaigns": [], "orders": [] },
"version": "v8-short-text-or-short-code-lookup"
}
}
Per-Button Tracking
Every quick-reply button on the template is tracked individually inbuttonAnalytics.allButtons. A click is a reply from one of the broadcast’s recipients, within 48 hours of the send, whose text is exactly the button’s text (case-insensitive). conversionRate is clicks as a percentage of delivered messages:
{
"buttonAnalytics": {
"allButtons": [
{ "buttonText": "Shop Now", "clicks": 29, "conversionRate": 2.04 },
{ "buttonText": "View More", "clicks": 23, "conversionRate": 1.62 },
{ "buttonText": "Not Interested", "clicks": 11, "conversionRate": 0.77 }
],
"totalButtonClicks": 63
}
}
Link Tracking
When the template has a URL button that uses a FlowIQ short link,linkAnalytics.linkType is REDIRECT_LINK and totalClicks / clickThroughRate (clicks ÷ delivered) count the clicks on that link. For a plain URL button, linkType is NORMAL_LINK, isTrackable is false, totalClicks and clickThroughRate are null, and note reads “Direct URL - clicks not trackable”.
Opt-Out Detection
A reply within 48 hours of the broadcast counts as an opt-out when the whole message (trimmed, case-insensitive) is one of these keywords. Opt-outs appear inconversionFunnel.optOuts and customerResponses.breakdown.optOuts.
| Language | Keywords |
|---|---|
| English | stop, unsubscribe, opt out, optout |
| Portuguese | parar, pare, cancelar |
| Spanish | detener, basta |
| French | arrêter, arreter, désabonner, desabonner |
| Italian | fermare, ferma, annullare, annulla, cancellare |
| General | quit, end, cancel |
Broadcasts Without Analytics
If thebroadcast_id doesn’t belong to your organization, or the broadcast predates FlowIQ’s structured template format, the response is still 200 but data carries an error instead of the fields above:
{
"success": true,
"data": {
"error": "BROADCAST_NOT_MIGRATED",
"message": "This broadcast has not been migrated to structured format",
"suggestion": "Run migration first or use v2 analytics",
"broadcast_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"revenue": { "totalRevenue": 0, "currency": null, "orderCount": 0, "avgOrderValue": 0, "utmCampaign": null, "orders": [] }
}
}
data.error before reading data.deliveryStats.Revenue Attribution
Revenue is automatically attributed by tracing the broadcast’s shortcode through to Shopify orders:- Broadcast button parameter contains a shortcode
- Shortcode resolves to a redirect URL with a
utm_campaignparameter - Shopify orders with
utm_source=whatsappand a matchingutm_campaignare summed
{
"revenue": {
"totalRevenue": 19302.00,
"currency": "ZAR",
"orderCount": 11,
"avgOrderValue": 1754.73,
"utmCampaign": "31marstockup",
"utmCampaigns": [
{ "campaign": "31marstockup", "orderCount": 6, "revenue": 10880.00, "avgOrderValue": 1813.33 },
{ "campaign": "31marstockup_vm", "orderCount": 5, "revenue": 8422.00, "avgOrderValue": 1684.40 }
],
"orders": [
{ "orderName": "#28556", "totalPrice": 1650.00, "currency": "ZAR", "utmCampaign": "31marstockup_vm", ... }
]
}
}
utmCampaigns array breaks down revenue per UTM variant, so you can see exactly how much came from the main link vs the “View More” button (_vm suffix). Each order also includes its own utmCampaign field.
_viewmore, _view_more, and _vm (case-insensitive). This captures orders from “View More” button clicks on the same campaign.Overall Broadcast Analytics
Passstart_date and end_date (YYYY-MM-DD) to get aggregated analytics across all broadcasts in the date range. Both days are included in full and read in UTC (end_date=2026-03-31 covers everything up to 23:59:59 UTC on 31 March). An end_date before start_date returns a 400.
curl "https://api.flowiq.live/broadcast-analytics?start_date=2026-03-01&end_date=2026-03-31" \
-H "Authorization: Bearer fiq_YOUR_API_KEY"
Response Fields
| Field | Description |
|---|---|
overallStats | Aggregated totals: sent, delivered, read, failed, rates, campaign count |
campaignPerformance | Per-broadcast breakdown, newest first: id, name, templateName, createdAt, recipients, sent, delivered, read, failed, deliveryRate, readRate, mediaUrl, category (the template’s category when sent: marketing, utility or authentication; null for sends under 10 recipients) and feeZar (estimated Meta messaging fee in ZAR, null when the category is unknown) |
timeSeriesData | Daily breakdown of sent/delivered/read/failed |
templateAnalytics | Per-template aggregated stats and usage counts |
errorAnalysis | Ranked error codes with counts and percentages |
POST Method
The same analytics are also available via POST, with parameters in the request body instead of query string:curl -X POST "https://api.flowiq.live/broadcast-analytics" \
-H "Authorization: Bearer fiq_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "broadcast_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }'
curl -X POST "https://api.flowiq.live/broadcast-analytics" \
-H "Authorization: Bearer fiq_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "start_date": "2026-03-01", "end_date": "2026-03-31" }'
Integration Example
async function getBroadcastAnalytics(apiKey, broadcastId) {
const response = await fetch(
`https://api.flowiq.live/broadcast-analytics?broadcast_id=${broadcastId}`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
},
}
);
const data = await response.json();
if (!response.ok) throw new Error(data.message || data.error);
if (data.data.error) throw new Error(data.data.message); // e.g. BROADCAST_NOT_MIGRATED
return data;
}
const result = await getBroadcastAnalytics(
"fiq_YOUR_API_KEY",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
);
console.log(`Delivered: ${result.data.deliveryStats.delivered}`);
console.log(`Revenue: ${result.data.revenue.totalRevenue}`);
console.log(`Buttons: ${result.data.buttonAnalytics.allButtons.length} tracked`);
console.log(`Link clicks: ${result.data.linkAnalytics.totalClicks}`);
Revenue Attribution
utm_campaign parameter, and your Shopify store to be connected. Orders are matched where utm_source=whatsapp in the landing site URL.Authorizations
Your FlowIQ API key (from Settings → Profile → API Keys), sent as a bearer token. Format: Bearer fiq_YOUR_API_KEY
Query Parameters
UUID of a specific broadcast to get detailed analytics for. If provided, start_date and end_date are ignored.
First day for overall analytics, YYYY-MM-DD (inclusive, from 00:00 UTC). Required when broadcast_id is not provided.
Last day for overall analytics, YYYY-MM-DD (inclusive, to 23:59:59 UTC). Must not be before start_date. Required when broadcast_id is not provided.
Response
Analytics data returned successfully
true
Analytics data. Individual mode: broadcastDetails, deliveryStats, conversionFunnel, buttonAnalytics, linkAnalytics, customerResponses, revenue, version (or error when the broadcast has no analytics). Overall mode: overallStats, campaignPerformance, timeSeriesData, templateAnalytics, errorAnalysis.

