Approval Workflows
on WhatsApp
Your campaign manager, DAM or CMS sends artwork. Clients tap Approve or Reject on their phone. Reasons captured. Results flow back — automatically.
The flow, end to end
Image, GIF, video or document + who to send to
Media header with Approve / Reject buttons
Reject asks "why?" — the next reply is captured as the reason
approval.approved / approval.rejected with your externalRef
Step 1 — Send an approval request
Single recipient, single artwork:
{
"callbackUrl": "https://your-system.co.za/webhooks/chatreach",
"items": [{
"to": "27821234567",
"mediaType": "image",
"mediaUrl": "https://your-cdn.co.za/artwork/summer-banner-v3.png",
"caption": "Summer campaign banner v3 — please review",
"externalRef": "ARTWORK-8841",
"approveLabel": "Approve ✅",
"rejectLabel": "Needs changes"
}]
}Bulk — multiple artworks to multiple reviewers in ONE call:
{
"items": [
{ "toList": ["27821234567", "27829876543"],
"mediaType": "image", "mediaUrl": "https://cdn/banner-a.png",
"externalRef": "ART-001" },
{ "to": "27821234567",
"mediaType": "video", "mediaUrl": "https://cdn/promo.mp4",
"externalRef": "ART-002" },
{ "to": "27821234567",
"mediaType": "document", "mediaUrl": "https://cdn/quote-4491.pdf",
"caption": "Quote #4491 — R48,500 incl VAT",
"externalRef": "QUOTE-4491",
"approveLabel": "Accept quote", "rejectLabel": "Decline" }
]
}{
"batchId": "a1b2c3d4e5f6",
"count": 4,
"approvals": [
{ "approvalId": "…", "to": "27821234567", "externalRef": "ART-001", "status": "sent" },
…
]
}Notes: GIFs send as mediaType: "video" (WhatsApp plays them looping). Media URLs must be publicly reachable. phoneLineId is optional — each integration can run on its own WhatsApp number, or omit it to use your default line. Button labels max 20 characters.
Step 2 — Receive the outcome
Delivered to your tenant webhooks AND the per-request callbackUrl if you set one:
{
"event": "approval.rejected",
"data": {
"approvalId": "9f8e7d6c-…",
"externalRef": "ARTWORK-8841",
"batchId": "a1b2c3d4e5f6",
"to": "27821234567",
"mediaUrl": "https://your-cdn.co.za/artwork/summer-banner-v3.png",
"status": "rejected",
"rejectionReason": "The logo is too small and please use the blue variant",
"respondedAt": "2026-07-11T09:42:15Z"
}
}Approvals deliver the same shape with "status": "approved" and no reason. Match on externalRef — it's your ID, echoed untouched.
Step 3 — Poll if you prefer
GET /api/approvals?batchId=a1b2c3d4e5f6
GET /api/approvals?externalRef=ARTWORK-8841
GET /api/approvals/{approvalId}
GET /api/approvals?status=rejected&days=7Status lifecycle
| Status | Meaning |
|---|---|
sent | Delivered to WhatsApp, awaiting a button tap |
approved | Recipient tapped Approve — webhook fired |
awaiting_reason | Tapped Reject; we asked why and are waiting for their reply |
rejected | Reason captured — webhook fired with rejectionReason |
failed | Send failed — errorMessage explains (bad number, media unreachable, etc.) |
Need more than two buttons?
Approvals are the streamlined two-button case. For "Approve / Reject / Request changes", option lists, or capturing photo replies, use the Interactions API — same idea, fully flexible.
Wire it up on the free trial
Full API access from day one. Your first approval flow can be live today.
Start Free Trial