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

1
Your system POSTs the media
Image, GIF, video or document + who to send to
2
Recipient sees it on WhatsApp
Media header with Approve / Reject buttons
3
They tap a button
Reject asks "why?" — the next reply is captured as the reason
4
Your webhook fires
approval.approved / approval.rejected with your externalRef

Step 1 — Send an approval request

Single recipient, single artwork:

POST /api/approvals
{
  "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:

POST /api/approvals (bulk)
{
  "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" }
  ]
}
Response
{
  "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:

approval.rejected → your endpoint
{
  "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
GET /api/approvals?batchId=a1b2c3d4e5f6
GET /api/approvals?externalRef=ARTWORK-8841
GET /api/approvals/{approvalId}
GET /api/approvals?status=rejected&days=7

Status lifecycle

StatusMeaning
sentDelivered to WhatsApp, awaiting a button tap
approvedRecipient tapped Approve — webhook fired
awaiting_reasonTapped Reject; we asked why and are waiting for their reply
rejectedReason captured — webhook fired with rejectionReason
failedSend 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