COD confirmations
Ask customers to confirm orders, change delivery times, and explain delivery refusals.
Create a cash-on-delivery confirmation from your store backend with POST /cod-confirmations. Read the current answers with GET /cod-confirmations/{confirmationId} or receive signed webhooks.
How the flow works
Submit a confirmation request
Send the customer's phone number, sender ID, template, parameters, and order reference. HTTP 202 accepts the request; it does not mean the customer has answered.
The customer receives a WhatsApp message
The confirmation template has three quick-reply buttons in this order: confirm, cancel, change time. Your template supplies the visible button labels.
The customer answers
A confirm or cancel tap records the answer. If configured, one reminder resends the template while waiting for an answer. No answer before the configured deadline records no_response.
Collect a changed delivery time
A change-time tap asks for a delivery day, then a time range. Typed text during rescheduling completes the flow with a note and any choices already collected. Follow-up steps expire 23 hours after the customer's last message; expiry completes with partial answers, so date and time range can be null.
Receive the outcome
Watification sends an outcome webhook to each enabled endpoint. Later taps on the original template can change completed answers and produce another event. Compare data.occurredAt when applying updates.
Template requirements
Both templates must be APPROVED, category UTILITY, and standard templates on the sender's WhatsApp Business account. Use a text header or no header; media headers are unsupported. No buttons other than the required quick replies are allowed.
| Template | Button positions |
|---|---|
| Confirmation | Exactly 3: confirm, cancel, change time. |
| Refusal check | Exactly 2: yes (I refused), no (I did not). |
The API assigns actions by position. Ensure your button text matches those actions.
Dashboard setup checklist
Open Sales > COD confirmation > Overview and complete the setup items:
- Set your delivery time zone: open Settings, choose an IANA Time zone, and save.
- Add follow-up texts for a language: configure texts for the exact template language. Include Day question, Time question, Refusal reason question, Reasons list button label, Other label, and Note prompt.
- Approve a confirmation template on a WhatsApp number: use Create template to submit a ready-made English or Arabic template, or use your own compatible template. Wait for approval and refresh Overview.
- Approve a refusal template if you will use refusal checks.
- Create an API key and enable a webhook endpoint under Developers.
In Settings, use Reminder and Mark as no response after for each check type. Reminders are off by default; deadlines default to 1,440 minutes. Reminder values are 15-4,320 minutes, deadlines are 60-10,080 minutes, and a deadline must be later than its reminder. Timings start when the request's message submission completes, rather than when WhatsApp reports delivery.
Configure Delivery days offered (1-3 starting tomorrow, with up to 6 weekdays skipped), 1-3 time-range labels of at most 20 characters, and 1-9 refusal reasons of at most 24 characters per language. The system adds the other-reason choice. Closing message (optional) is sent at most once per check on reply completion when configured; timer expiry does not send it. Select Save settings.
Create a confirmation
Set WATIFICATION_API_KEY on your server and replace all fictional values. The example uses the ready-made cod_confirm template's four positional body parameters: customer, order, total, and address.
curl --fail-with-body --request POST \
'https://api.watification.com/api/v1/cod-confirmations' \
--header "Authorization: Bearer $WATIFICATION_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: order-example-1042' \
--data '{
"to": "+15555550123",
"senderId": "11111111-1111-4111-8111-111111111111",
"template": { "name": "cod_confirm", "language": "en" },
"parameters": { "body": ["Alex", "1042", "45 USD", "123 Example Street"] },
"reference": "1042"
}'| Field | Required | Value |
|---|---|---|
to | Yes | Valid phone with leading +, normalized to E.164. |
senderId | Yes | Watification sender UUID in your workspace. |
template.name | Yes | Non-empty template name. |
template.language | Yes | Exact language code with configured follow-up texts. |
parameters | No | Values matching the template; omit when none are needed. |
parameters.header | If needed | String, present exactly when the text header has a parameter. |
parameters.body | If needed | Positional string array or named string object. |
reference | Yes | Order reference, 1-128 characters; not unique. |
Body parameters must match the template's count or exact names. Each header/body value must contain non-whitespace text and be at most 1,024 characters. Do not supply button parameters. Unknown fields or query parameters are rejected.
{
"parameters": {
"body": ["Alex", "1042", "45 USD", "123 Example Street"]
}
}HTTP 202 returns:
{
"confirmationId": "33333333-3333-4333-8333-333333333333",
"status": "accepted",
"reference": "1042"
}Read answers and status
curl --fail-with-body \
'https://api.watification.com/api/v1/cod-confirmations/33333333-3333-4333-8333-333333333333' \
--header "Authorization: Bearer $WATIFICATION_API_KEY"HTTP 200 returns:
{
"confirmationId": "33333333-3333-4333-8333-333333333333",
"reference": "1042",
"to": "+15555550123",
"status": "rescheduled",
"answeredAt": "2026-10-09T10:05:00.000Z",
"reschedule": {
"date": "2026-10-10",
"timeRange": "9 AM - 1 PM",
"note": null
},
"refusalCheck": null,
"failure": null,
"createdAt": "2026-10-09T10:00:00.000Z"
}| Field | Meaning |
|---|---|
confirmationId, reference, to | Confirmation UUID, your order reference, and recipient. |
status | Confirmation state from the table below. |
answeredAt | ISO timestamp or null; partial follow-up expiry can set this timestamp. |
reschedule | null or { date, timeRange, note }; each value may be null. Date is YYYY-MM-DD in your delivery time zone. |
refusalCheck | null or { status, reason, note, answeredAt }; answer values can be null. |
failure | null or { code, message } for the most recently failed confirmation or refusal check. |
createdAt | ISO creation timestamp. |
| Confirmation status | Meaning |
|---|---|
accepted | Reserved; initial submission not complete. |
awaiting_answer | Submitted and waiting for a customer tap. |
confirmed | Customer confirmed. |
cancelled | Customer cancelled. |
rescheduling | Collecting a delivery day or time range. |
rescheduled | Reschedule completed, possibly with partial choices or a note. |
no_response | Initial answer deadline passed. A later tap can still update it. |
failed | Submitted initial or reminder message failed while awaiting an answer. |
Check a reported delivery refusal
Start at most one refusal check per confirmation. The original confirmation must exist in your workspace, have a conversation, and be neither accepted nor failed. The check uses its original sender and recipient.
WATIFICATION_BASE_URL='https://api.watification.com/api/v1'
CONFIRMATION_ID='33333333-3333-4333-8333-333333333333'
curl --fail-with-body --request POST \
"$WATIFICATION_BASE_URL/cod-confirmations/$CONFIRMATION_ID/refusal-check" \
--header "Authorization: Bearer $WATIFICATION_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: refusal-example-1042' \
--data '{
"template": { "name": "cod_refusal", "language": "en" },
"parameters": { "body": ["Alex", "1042"] }
}'The request accepts only template and optional parameters, with the same rules as creation. HTTP 202 returns:
{
"confirmationId": "33333333-3333-4333-8333-333333333333",
"refusalCheck": { "status": "accepted" }
}If the customer says yes, the flow asks for a reason from a list. The other-reason choice asks for a typed note. Typed text during either the reason or note step also completes with a note. Follow-up expiry completes with the collected parts, possibly a null reason.
| Refusal status | Meaning |
|---|---|
accepted | Reserved; refusal submission not complete. |
awaiting_answer | Waiting for yes/no. |
awaiting_reason | Customer said yes; waiting for a reason. |
awaiting_note | Waiting for an other-reason note. |
denied | Customer said they did not refuse. |
refusal_confirmed | Reason/note step completed, including partial expiry. |
no_response | Initial refusal-answer deadline passed. |
failed | Refusal initial or reminder message failed while awaiting an answer. |
Idempotency, quotas, and events
Both POST routes accept an optional Idempotency-Key of at most 128 characters. Use a different non-empty key per logical operation. Matching repeats return the original acceptance response without another message or quota unit. All request fields are compared after phone/UUID normalization; named object key order does not matter. Changed values return 409 IDEMPOTENCY_KEY_REUSED. Keys are workspace-scoped within each operation family.
Confirmation creation and refusal checks have separate monthly quotas. Each accepted request consumes one unit from its own allowance. Repeats, reminders, follow-up questions, and closing messages consume no additional COD quota. Later failures are not refunded. Zero or exhausted allowance returns 429 COD_QUOTA_EXCEEDED. See Sales > COD confirmation > Overview for allowances and Log for answers.
Confirmation events: cod.confirmed, cod.cancelled, cod.reschedule_requested, cod.no_response, cod.failed.
Refusal events: cod.refusal_confirmed, cod.refusal_denied, cod.refusal_no_response, cod.refusal_failed.
Each contains the GET response fields plus data.occurredAt. Starting a reschedule or reason question does not itself emit an outcome. See the event catalog.
Endpoint errors
See rate limits and errors for recovery and Problem Details.
| HTTP | Codes | Meaning |
|---|---|---|
| 401 | API_KEY_INVALID | Missing, invalid, or revoked key. |
| 404 | COD_SENDER_NOT_FOUND, COD_TEMPLATE_NOT_FOUND, COD_CONFIRMATION_NOT_FOUND | Requested resource not found in this workspace. |
| 409 | COD_NOT_CONFIGURED | Delivery time zone not configured. |
| 409 | COD_LANGUAGE_NOT_CONFIGURED | Template language lacks follow-up texts. |
| 409 | COD_SENDER_UNAVAILABLE, COD_TEMPLATE_NOT_USABLE | Sender or template cannot be used. |
| 409 | COD_CONFIRMATION_NOT_ELIGIBLE | Original confirmation cannot start a refusal check. |
| 409 | COD_REFUSAL_CHECK_EXISTS | A refusal check already exists. |
| 409 | IDEMPOTENCY_KEY_REUSED | Key matches a different request. |
| 422 | COD_TEMPLATE_PARAMETERS_INVALID | Parameter shape, count, names, or values do not match. |
| 422 | DEVELOPER_INVALID_INPUT, RECIPIENT_BLOCKED | Invalid input or blocked recipient. |
| 429 | COD_QUOTA_EXCEEDED, RATE_LIMITED | Monthly allowance or per-key request limit reached. |