Product Keep your app Developers Pricing Contact Sign in Start free for 14 days

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