Skip to main content

Waplify API — Complete Reference

This page contains every endpoint, request/response schema, and example in one place. Copy it and paste into any AI tool to generate integration code instantly.

AI-ready API reference

Copy this page and paste into ChatGPT, Claude, or Cursor to generate integration code

Setup

ItemValue
Base URLhttps://server.waplify.io
Auth HeaderAuthorization: Bearer wapl_your_api_key
Content-Typeapplication/json
Rate Limit100 requests/minute per API key
Phone FormatCountry code + number, no + sign (e.g., 911234567890)

1. Send Template Message

Send a WhatsApp message using an approved template. Works anytime (no 24-hour window restriction).

POST /api/v1/messages/send

Request:

{
"template_name": "order_confirmation",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"body_data": {
"1": "John",
"2": "Premium Plan",
"3": "99"
},
"header_data": {
"1": "Daily Report"
},
"media_url": "https://example.com/image.png",
"filename": "Report.pdf",
"waba_phone_id": "123456789"
}
FieldRequiredDescription
template_nameYesTemplate name (case-sensitive)
contact_phoneYesWith country code, no +
contact_nameNoDefaults to "User #random"
body_dataNoTemplate body variables (required if template has {{1}} etc.)
header_dataNoFor TEXT header variables only
media_urlConditionalRequired if template has IMAGE/VIDEO/DOCUMENT header
filenameNoFor document templates
waba_phone_idNoDefaults to user's primary number

Response (200):

{
"status": "success",
"message": "Message sent successfully",
"message_id": "wamid.HBgNOTE4MDMxMjM0NTY3OA==",
"contact_id": "507f1f77bcf86cd799439012",
"template_name": "order_confirmation",
"timestamp": "2026-06-15T10:00:00Z"
}

Errors: 400 (missing variables, invalid phone, template not approved, media required), 401 (invalid API key), 404 (template not found), 429 (rate limit)

IMPORTANT — "success" means accepted, not delivered:

  • A "status": "success" response means WhatsApp has accepted the message — it does NOT mean it was delivered
  • Actual delivery status arrives later via webhooks: message.sentmessage.deliveredmessage.read
  • If delivery fails, you receive a message.failed webhook
  • You MUST set up webhooks to track actual delivery

Notes:

  • Contact auto-created if phone number doesn't exist
  • Variables can be positional ("1", "2") or named ("name", "amount")
  • Media limits: Image 5MB (JPEG/PNG), Video 16MB (MP4), Document 100MB (PDF/DOC/DOCX/PPT/PPTX/TXT)

2. Send Free-Form Message

Send a text or media message without a template. Only works within 24-hour window (contact must have messaged you in last 24 hours).

POST /api/v1/messages/send-message

Request:

{
"contact_phone": "911234567890",
"contact_name": "John Doe",
"message_type": "text",
"message": "Hello! How can I help?",
"media_url": "https://example.com/file.pdf",
"caption": "Here is the document",
"filename": "Report.pdf",
"waba_phone_id": "123456789"
}
FieldRequiredDescription
contact_phoneYesWith country code, no +
contact_nameNoDefaults to "User #random"
message_typeYestext, image, video, audio, or document
messageFor textText message body (max 4,096 chars)
media_urlFor mediaRequired for image/video/audio/document
captionNoCaption for image/video/document
filenameNoFor document type
waba_phone_idNoDefaults to user's primary number
buttonsNoInteractive buttons object — text only. See below

Response (200):

{
"status": "success",
"message": "Message sent successfully",
"message_id": "wamid.HBgNOTE4MDMxMjM0NTY3OA==",
"contact_id": "507f1f77bcf86cd799439012",
"message_type": "text",
"timestamp": "2026-06-15T10:00:00Z"
}

Errors: 400 (invalid phone, missing message/media), 401 (invalid API key), 403 (24-hour window closed — use template endpoint instead), 429 (rate limit)

Note: 403 errors use detail field: {"detail": "Cannot send free-form message: ..."}

Media limits: Image 5MB (JPEG/PNG), Video 16MB (MP4), Audio 16MB (AAC/MP3/M4A/AMR/OGG), Document 100MB (PDF/DOC/DOCX/PPT/PPTX/TXT)

Interactive buttons: Add a buttons object to a text message — up to 3 reply buttons OR one website button (never both). Requires message_type: text, message ≤ 1,024 chars, and an open 24-hour window.

{
"contact_phone": "911234567890",
"message_type": "text",
"message": "Did this answer your question?",
"buttons": {
"type": "reply",
"reply_buttons": [
{ "id": "yes", "title": "Yes, thanks" },
{ "id": "no", "title": "No, I need help" }
]
}
}

buttons.type: replyreply_buttons[] of { id, title } (1–3 items; title ≤ 20 chars, unique). cta_urlurl_button of { display_text, url } (display_text ≤ 20 chars; url must start with http(s)://).


3. List Templates

Get all WhatsApp message templates on the account (approved and otherwise).

GET /api/v1/templates/

Response (200):

{
"status": "success",
"templates": [
{
"id": "507f1f77bcf86cd799439012",
"name": "order_confirmation",
"category": "UTILITY",
"language": "en_US",
"status": "APPROVED",
"header_format": "IMAGE",
"body": "Hello {{1}}, your order {{2}} is confirmed for {{3}}.",
"requires_media": true,
"variable_count": 3,
"parameter_format": "POSITIONAL",
"is_carousel": false,
"created_at": "2026-06-15T10:00:00Z"
}
],
"total": 1
}

Key fields per template: status (APPROVED/PENDING/REJECTED), header_format (NONE/TEXT/IMAGE/VIDEO/DOCUMENT/LOCATION), requires_media (true = need media_url), body (parse {{1}}/{{name}} for variables), parameter_format (POSITIONAL or NAMED), is_carousel (true = use carousel_data when sending), carousel_cards (card definitions), limited_time_offer + offer_code (LTO templates need expiration_time_ms when sending).


4. Create Template

Create a new text/media-header template and submit it to Meta. Use endpoint 5 for carousel templates.

POST /api/v1/templates/create

Request:

{
"name": "order_update",
"category": "UTILITY",
"language": "en_US",
"waba_phone_id": "123456789012345",
"header": { "format": "TEXT", "text": "Order Update" },
"media_url": "https://example.com/banner.jpg",
"body": {
"text": "Hello {{1}}, your order {{2}} has been shipped!",
"examples": { "1": "John", "2": "ORD-12345" }
},
"footer": { "text": "Thank you" },
"buttons": [
{ "type": "URL", "text": "Track", "url": "https://example.com/{{1}}", "url_example": "ORD-12345" },
{ "type": "QUICK_REPLY", "text": "Contact us" }
],
"limited_time_offer": { "text": "Limited offer", "has_expiration": true },
"offer_code": "FLASH40",
"add_security_recommendation": false,
"code_expiration_minutes": 10,
"message_send_ttl_seconds": 3600
}
FieldRequiredDescription
nameYesLowercase alphanumeric + underscores only
categoryYesMARKETING, UTILITY, or AUTHENTICATION
languageNoDefaults to en_US
waba_phone_idNoTarget WABA phone ID. Falls back to the first number linked to your account.
headerNo{ format, text?, text_example? }. Formats: TEXT/IMAGE/VIDEO/DOCUMENT/LOCATION
media_urlConditionalRequired if header format is IMAGE/VIDEO/DOCUMENT — public URL, downloaded and uploaded to Meta
bodyConditionalRequired for MARKETING/UTILITY. { text, examples? }. Max 1024 chars.
footerNo{ text } (max 60 chars, no variables)
buttonsNoUp to 10. Types: QUICK_REPLY (max 10), URL (max 2), PHONE_NUMBER (max 1), COPY_CODE (max 1), FLOW
limited_time_offerNoMARKETING-only. Requires offer_code + COPY_CODE button at index 0, no footer, body ≤ 600 chars
offer_codeConditionalRequired for LTO templates (max 15 chars)
add_security_recommendationNoAUTHENTICATION only — adds security text
code_expiration_minutesNoAUTHENTICATION only (1–90)
message_send_ttl_secondsNoTTL for messages sent with this template

Response (201):

{
"status": "success",
"message": "Template 'order_update' created and submitted for review",
"template_id": "507f1f77bcf86cd799439012",
"meta_template_id": "1234567890123456",
"meta_status": "PENDING"
}

Constraints:

  • Cannot mix numbered ({{1}}) and named ({{name}}) variables in the same template
  • WhatsApp domains (whatsapp.com, wa.me) are rejected in URL buttons
  • Templates start as PENDING; call endpoint 6 (sync) or endpoint 3 (list) to refresh approval status

Errors: 422 (validation — name format, mixed variables, missing body), 400 (media download fail, duplicate name), 401 (invalid key), 429 (rate limit)


Create a media-card carousel template (always MARKETING, 2–10 cards, same header format and button structure across cards).

POST /api/v1/templates/create-carousel

Request:

{
"name": "product_showcase",
"language": "en_US",
"waba_phone_id": "123456789012345",
"body": { "text": "Hi {{1}}, check out our top picks!", "examples": { "1": "John" } },
"cards": [
{
"header_format": "IMAGE",
"media_url": "https://example.com/product1.jpg",
"body_text": "Product 1 - Amazing.",
"buttons": [
{ "type": "URL", "text": "Shop", "url": "https://example.com/{{1}}", "url_example": "p1" },
{ "type": "QUICK_REPLY", "text": "More like this" }
]
},
{
"header_format": "IMAGE",
"media_url": "https://example.com/product2.jpg",
"body_text": "Product 2 - Best seller.",
"buttons": [
{ "type": "URL", "text": "Shop", "url": "https://example.com/{{1}}", "url_example": "p2" },
{ "type": "QUICK_REPLY", "text": "More like this" }
]
}
],
"message_send_ttl_seconds": 3600
}

Card rules: 2–10 cards; all cards must share the same header_format (IMAGE or VIDEO); all cards must have the same button types in the same order; either all or no cards carry body_text (max 160 chars each); each card needs 1–2 buttons.

Response (201): Same shape as Create Template (status, template_id, meta_template_id, meta_status).


6. Sync Templates with Meta

Pull the latest template state from Meta into Waplify. Creates missing templates locally, updates changed ones, removes deleted ones. Runs across every WABA phone linked to the account.

POST /api/v1/templates/sync

No request body.

Response (200):

{
"status": "success",
"message": "Template sync completed",
"summary": {
"total_meta_templates": 12,
"synced": 12,
"created": 1,
"updated": 3,
"skipped": 8,
"deleted": 0
}
}

Use after: creating a template (to pick up Meta's APPROVED/REJECTED decision), or editing templates in the Meta Business Manager.


7. Create Campaign

Create a WhatsApp campaign from a list of phone numbers using an approved template. Phone numbers are normalized + upserted as contacts automatically.

POST /api/v1/campaigns/

Request:

{
"name": "Summer Sale Blast",
"description": "Announce the summer sale",
"template_name": "summer_sale_promo",
"contacts": [
{ "phone": "911234567890", "name": "John Doe" },
{ "phone": "919876543210", "name": "Priya Singh" }
],
"body_data": { "1": "Loyalty member", "2": "40%" },
"header_data": { "1": "Summer" },
"media_url": "https://example.com/banner.jpg",
"filename": "promo.pdf",
"waba_phone_id": "123456789012345",
"scheduled_at": "2026-05-15T09:30:00Z",
"scheduled_timezone": "Asia/Kolkata",
"expiration_time_ms": 1209600000,
"carousel_data": [
{ "media_url": "https://example.com/c1.jpg", "url_button_params": ["p1"], "button_payloads": ["more-like-this"] },
{ "media_url": "https://example.com/c2.jpg", "url_button_params": ["p2"], "button_payloads": ["more-like-this"] }
]
}
FieldRequiredDescription
nameYesCampaign name
template_nameYesMust be APPROVED and owned by the user
contactsYes1–10,000 entries { phone, name? }. Phones normalized + upserted.
body_dataNoApplied to every recipient (same format as Send Template)
header_dataNoTEXT-header variables
media_urlConditionalRequired if template has IMAGE/VIDEO/DOCUMENT header
filenameNoFor DOCUMENT media
waba_phone_idNoFalls back to template's, then user's first
scheduled_atNoISO 8601. Omit to create as draft.
scheduled_timezoneNoIANA string, e.g. Asia/Kolkata
expiration_time_msConditionalRequired for limited-time offer templates
carousel_dataConditionalRequired for carousel templates; one entry per card, in order

Response (200):

{
"status": "success",
"message": "Campaign created successfully",
"campaign_id": "507f1f77bcf86cd799439060",
"name": "Summer Sale Blast",
"status_value": "draft",
"total_contacts": 2,
"invalid_phones": [],
"timestamp": "2026-06-15T10:00:00Z"
}
  • status_value: draft (needs manual send) or scheduled (auto-sends at scheduled_at)
  • invalid_phones: phones that failed normalization and were skipped
  • Duplicate phones are deduplicated automatically

Not supported via this endpoint: ORDER_DETAILS and FLOW buttons; per-contact variable overrides (body_data is applied to every recipient).

Errors: 404 (template not found), 400 (template not APPROVED, missing body variables, media unreachable, no valid phones, LTO needs expiration_time_ms), 403 (no access to WABA phone), 429 (rate limit)


8. Send Campaign

Run prechecks on a draft campaign and queue it in the background worker. Asynchronous — returns immediately with a task ID.

POST /api/v1/campaigns/{campaign_id}/send

No body.

Response (202):

{
"status": "accepted",
"message": "Campaign send queued and will run in background",
"campaign_id": "507f1f77bcf86cd799439060",
"task_id": "f5a1f9d4-2c5c-4c9f-9d1b-3e2d1a1f9d4f",
"timestamp": "2026-06-15T10:00:00Z"
}

When to call: only for draft campaigns. Scheduled campaigns auto-send at scheduled_at and don't need this endpoint.

Errors: 400 (invalid campaign_id, precheck failed — template no longer approved, insufficient credits, etc.), 404 (campaign not found), 429 (rate limit)

After queueing: track per-message delivery via webhooks (message.sent, message.delivered, message.read, message.failed) and aggregate stats via endpoint 9.


9. Campaign Stats

Return aggregate per-contact delivery stats for a campaign.

GET /api/v1/campaigns/{campaign_id}/stats

Response (200):

{
"status": "success",
"campaign_id": "507f1f77bcf86cd799439060",
"name": "Summer Sale Blast",
"campaign_status": "completed",
"total_contacts": 2000,
"sent_count": 1998,
"delivered_count": 1932,
"read_count": 1204,
"clicked_count": 312,
"failed_count": 2,
"replied_count": 41,
"success_count": 1998,
"created_at": "2026-06-15T10:00:00Z",
"scheduled_at": null,
"started_at": "2026-06-15T10:05:00Z",
"completed_at": "2026-06-15T10:45:00Z"
}
FieldMeaning
campaign_statusdraft, scheduled, running, completed, cancelled, failed
sent_countWhatsApp accepted the send
delivered_countReached the contact's phone
read_countContact opened the message
clicked_countContact clicked a URL / quick-reply button
failed_countSend/delivery failed at any stage
replied_countContact sent a reply back
success_counttotal_contacts - failed_count

Funnel: total_contacts → sent → delivered → read → clicked. Counters update continuously as the worker progresses.

Errors: 400 (invalid campaign_id), 404 (not found), 429 (rate limit)


10. List Contacts

GET /api/v1/contacts/?page=1&limit=20&search=john

ParamDefaultDescription
page1Page number
limit20Per page (max 100)
searchSearch name, phone, or email

Response (200):

{
"status": "success",
"contacts": [
{
"id": "507f1f77bcf86cd799439012",
"first_name": "John",
"last_name": "Doe",
"phone_number": "911234567890",
"email": "john@example.com",
"company": "Acme Corp",
"tags": ["vip"],
"opted_in": true,
"source": "api",
"created_at": "2026-06-15T10:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}

11. Create Contact

POST /api/v1/contacts/

Request:

{
"phone_number": "911234567890",
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"company": "Acme Corp",
"tags": ["vip", "newsletter"]
}

Fields: phone_number (required), first_name (required), last_name (optional), email (optional), company (optional), tags (optional).

Response (201):

{
"status": "success",
"message": "Contact created successfully",
"contact": { /* same fields as list response */ }
}

12. Get Contact

GET /api/v1/contacts/{contact_id}

Response (200): Same as create contact response.


13. Delete Contact

DELETE /api/v1/contacts/{contact_id}

Response (200):

{
"status": "success",
"message": "Contact deleted successfully",
"contact_id": "507f1f77bcf86cd799439012"
}

Note: Soft delete — contact deactivated, not permanently removed.


14. List Groups

GET /api/v1/groups/?page=1&limit=20&search=vip

Response (200):

{
"status": "success",
"groups": [
{
"id": "507f1f77bcf86cd799439050",
"name": "VIP Customers",
"description": "High-value customers",
"tags": ["vip"],
"contact_count": 42,
"created_at": "2026-06-15T10:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}

15. Create Group

POST /api/v1/groups/

Request:

{
"name": "VIP Customers",
"description": "High-value customers",
"tags": ["vip"],
"contact_ids": ["507f1f77bcf86cd799439012"]
}

Fields: name (required, unique per account), description (optional), tags (optional), contact_ids (optional — add contacts immediately).

Response (201):

{
"status": "success",
"message": "Contact group created successfully",
"group": { /* same fields as list response */ }
}

16. Get Group

GET /api/v1/groups/{group_id}


17. Delete Group

DELETE /api/v1/groups/{group_id}

Note: Contacts are NOT deleted — only removed from the group.


18. Add Contacts to Group

POST /api/v1/groups/{group_id}/contacts

Request:

{
"contact_ids": ["507f1f77bcf86cd799439012", "507f1f77bcf86cd799439013"]
}

Response (200):

{
"status": "success",
"message": "2 contact(s) added to group",
"group_id": "507f1f77bcf86cd799439050",
"contact_ids": ["507f1f77bcf86cd799439012", "507f1f77bcf86cd799439013"]
}

Duplicates are silently skipped.


19. Remove Contacts from Group

DELETE /api/v1/groups/{group_id}/contacts

Request:

{
"contact_ids": ["507f1f77bcf86cd799439012"]
}

Contacts are NOT deleted from your account — only removed from this group.


Webhooks

Configuration

Register endpoints at Developers > Webhooks in the dashboard.

SettingDefaultDescription
urlHTTPS URL to receive notifications
secretOptional HMAC-SHA256 signing secret
subscribed_eventsallWhich events to receive
retry_count3Max retries (0–10)
timeout5sRequest timeout (1–30s)

Events

EventWhen
message.sentMessage accepted by WhatsApp
message.deliveredDelivered to contact's phone
message.readRead by contact
message.failedDelivery failed
button.clickedButton interaction
message.receivedNew inbound message
message.replyReply to your outbound message

Payload — Message Status

{
"event": "message.delivered",
"timestamp": "2026-06-15T10:05:00",
"campaign_id": "507f1f77bcf86cd799439060",
"campaign_name": "Summer Promo",
"message": {
"message_id": "507f1f77bcf86cd799439070",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"status": "delivered",
"previous_status": "sent",
"status_timestamp": "2026-06-15T10:05:00"
}
}

For message.failed, adds: error_message (string), error_code (number).

Payload — Inbound Message

{
"event": "message.received",
"timestamp": "2026-06-15T10:15:00",
"message": {
"message_id": "wamid.HBgNOTE4MDMxMjM0NTY3OQ==",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"content": "Hi, I have a question",
"message_type": "text",
"timestamp": "2026-06-15T10:15:00",
"waba_phone_id": "123456789012345",
"is_reply": false,
"is_from_ad": false
}
}

Payload — Reply

{
"event": "message.reply",
"timestamp": "2026-06-15T10:10:00",
"campaign_id": "507f1f77bcf86cd799439060",
"campaign_name": "Summer Promo",
"original_message": {
"message_id": "507f1f77bcf86cd799439070",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"sent_at": "2026-06-15T10:00:00"
},
"reply": {
"message_id": "507f1f77bcf86cd799439071",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"content": "Yes, I'd like to know more!",
"message_type": "text",
"timestamp": "2026-06-15T10:10:00"
}
}

Signature Verification

Header: X-Webhook-Signature: sha256=<hex_digest>

Verify: HMAC-SHA256(your_secret, raw_request_body) must match the hex digest after sha256=.

Retry Policy

  • 2xx → success
  • 4xx (except 429) → not retried
  • 429/5xx/timeout → retried with exponential backoff (1s, 2s, 4s, 8s...)

Status Flow

pending → accepted → sent → delivered → read → clicked
↘ failed

Delivery Headers

Content-Type: application/json
User-Agent: Waplify-Webhook/1.0
X-Webhook-Signature: sha256=... (if secret configured)