Skip to main content

UC-022 — Bulk WhatsApp Template Campaign

FieldValue
IDUC-022
GoalCreate and send a bulk WhatsApp campaign with approved templates
ChannelWhatsApp
Complexity⭐⭐ Intermediate
Estimated time25 minutes
APIs involvedPOST /api/partner-gateway/v1/campaigns, PUT /campaigns/{id}, POST /campaigns/{id}/calculateGoal, GET /campaigns/{id}/price, PUT /campaigns/{id}/confirm

Real-world scenarios

  • ShopItalia — WhatsApp promotional notification: ShopItalia sends a "Summer sales -50%" promotion to 15,000 customers via a WhatsApp template with a "Go to shop" button and header image.
  • BeautyBox — Personalized coupons: Bulk send of personalized discount coupons with customer name and unique code, tracking opens and link clicks.
  • AssicuraSemplice — Policy expiry reminder: Automatic notification to customers with policies expiring in the next 30 days, with a button for online renewal.

WhatsApp campaign lifecycle

The diagram illustrates the cycle from draft to configuration with Meta-approved WhatsApp template, cost estimation, confirmation and monitoring of delivery and read metrics.

Prerequisites

  • Active API Key with campaign and WhatsApp channel permissions
  • WhatsApp template approved by Meta (see UC-013)
  • Verified and active WhatsApp Business number (see UC-025)
  • Contact list created and populated (see UC-007)

Step 1 — Create the campaign as a draft

Create a new campaign specifying the WhatsApp channel.

curl -X POST https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"name": "Saldi Estivi 2026",
"channel": "WHATSAPP",
"description": "Promozione saldi estivi -50% con coupon personalizzato"
}'

Response — Campaign created

{
"id": "camp-wa-2026-saldi-001",
"name": "Saldi Estivi 2026",
"channel": "WHATSAPP",
"status": "DRAFT",
"createdAt": "2026-04-09T10:00:00+02:00"
}

Step 2 — Configure the template and recipient list

Associate the approved WhatsApp template, the contact list and the template parameters with the campaign.

curl -X PUT https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/camp-wa-2026-saldi-001 \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"templateId": "promo_saldi_estivi_2026",
"contactListIds": ["list-clienti-attivi-2026"],
"sender": "ShopItalia",
"templateParams": {
"header": {
"type": "image",
"url": "https://cdn.shopitalia.it/promo/saldi-estate-2026.jpg"
},
"body": [
{"key": "1", "value": "{{nome}}"},
{"key": "2", "value": "50%"},
{"key": "3", "value": "30 giugno 2026"}
]
},
"scheduledDate": "2026-06-01T08:00:00.000+0200"
}'

Response — Campaign configured

{
"id": "camp-wa-2026-saldi-001",
"status": "CONFIGURED",
"templateId": "promo_saldi_estivi_2026",
"contactListIds": ["list-clienti-attivi-2026"],
"scheduledDate": "2026-06-01T08:00:00.000+0200"
}

:::tip Placeholders in template parameters The value {{nome}} is automatically replaced with the contact's firstName field from the list. Make sure contacts have this field populated. :::

Behind the scenes — Bulk WhatsApp sending and rate limiting
  1. Template validation: The gateway verifies that the template is in APPROVED status on Meta Business. Templates in PENDING or REJECTED status block the campaign.
  2. Parameter mapping: The body[].value parameters are mapped to the {{1}}, {{2}}, etc. placeholders defined in the template. The number of parameters must match exactly.
  3. Rate limiting: WhatsApp applies throughput limits based on the Business number tier (1K, 10K, 100K msg/day). The gateway automatically manages throttling to respect the limits.
  4. Conversation window: Sending a template opens a 24-hour conversation window. User reply messages within this window have no additional cost.
  5. Quality rating: Meta monitors the template's quality rating. High block rates can lead to template suspension. Monitor post-campaign metrics.

Step 3 — Estimate the cost and confirm

Calculate reachable recipients and the estimated cost.

# Calculate the goal
curl -X POST https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/camp-wa-2026-saldi-001/calculateGoal \
-H "X-Api-Key: YOUR_API_KEY"

# Get the cost estimate
curl -X GET https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/camp-wa-2026-saldi-001/price \
-H "X-Api-Key: YOUR_API_KEY"

Response — Cost estimate

{
"campaignId": "camp-wa-2026-saldi-001",
"totalContacts": 15000,
"reachableContacts": 14200,
"estimatedCost": 994.00,
"currency": "EUR",
"costPerMessage": 0.07
}
# Confirm and schedule the campaign
curl -X PUT https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/camp-wa-2026-saldi-001/confirm \
-H "X-Api-Key: YOUR_API_KEY"

Expected result

StepActionResult
1POST /campaignsCampaign created with status DRAFT
2PUT /campaigns/{id}Status CONFIGURED with template and list
3POST /calculateGoal14,200 reachable recipients out of 15,000
4GET /priceEstimated cost 994.00 EUR
5PUT /confirmCampaign confirmed, delivery scheduled

Complete end-to-end example

# 1. Create WhatsApp campaign
CAMP_ID=$(curl -s -X POST https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"name": "Saldi Estivi 2026",
"channel": "WHATSAPP",
"description": "Promozione saldi con coupon personalizzato"
}' | jq -r '.id')

echo "Campaign ID: $CAMP_ID"

# 2. Configure template and list
curl -s -X PUT "https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/${CAMP_ID}" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"templateId": "promo_saldi_estivi_2026",
"contactListIds": ["list-clienti-attivi-2026"],
"sender": "ShopItalia",
"templateParams": {
"body": [
{"key": "1", "value": "{{nome}}"},
{"key": "2", "value": "50%"},
{"key": "3", "value": "30 giugno 2026"}
]
}
}'

# 3. Calculate goal and check price
curl -s -X POST "https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/${CAMP_ID}/calculateGoal" \
-H "X-Api-Key: YOUR_API_KEY"

curl -s -X GET "https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/${CAMP_ID}/price" \
-H "X-Api-Key: YOUR_API_KEY" | jq .

# 4. Confirm
curl -s -X PUT "https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/${CAMP_ID}/confirm" \
-H "X-Api-Key: YOUR_API_KEY"

Variants

Campaign with dynamic coupon button

Template with a button containing a unique coupon code per recipient:

curl -X PUT "https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/${CAMP_ID}" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"templateId": "coupon_personalizzato_2026",
"contactListIds": ["list-clienti-vip"],
"templateParams": {
"body": [{"key": "1", "value": "{{nome}}"}],
"buttons": [{"index": 0, "type": "copy_code", "value": "{{coupon_code}}"}]
}
}'

Common errors

400 Bad Request — Template not approved by Meta

{
"status": "fail",
"data": {
"templateId": "Template not approved by Meta or not found for channel WHATSAPP"
}
}

Solution: Check the template status on Meta Business Manager. Only templates with APPROVED status can be used. See UC-013.

400 Bad Request — Template parameter mismatch

{
"status": "fail",
"data": {
"templateParams": "Parameter count mismatch: expected 3, got 2"
}
}

Solution: The number of parameters in the body field must exactly match the {{1}}, {{2}}, etc. placeholders defined in the approved template.

401 Unauthorized — Missing or invalid API Key

{
"status": "fail",
"data": {
"authentication": "Invalid or missing API key"
}
}

Solution: Verify that the X-Api-Key header is present and that the key is active in the platform dashboard.

Next steps

References