WhatsApp Business API для напоминания о возврате займа
Сценарий отправляет клиенту персонализированный шаблон WhatsApp перед датой погашения кредита или рассрочки.
Обзор сценария
Сценарий отправляет клиенту персонализированный шаблон WhatsApp перед датой погашения займа или очередного платежа. Сообщение включает имя клиента, сумму платежа и дату погашения. Статическая кнопка URL открывает страницу оплаты.
Пример шаблона
Здравствуйте, {{1}}! Взнос по вашему кредиту на сумму {{2}} нужно оплатить до {{3}}. Платите вовремя, чтобы избежать штрафов.
- {{1}}имя клиента
- {{2}}сумма взноса
- {{3}}срок погашения
- “Оплатить взнос”кнопка — задано в шаблоне Meta

Когда это использовать
Такой сценарий подходит, когда приближается срок платежа по кредиту или рассрочке, а клиент ещё не открыл разговор в WhatsApp — сначала можно отправить только одобренный шаблон. Это актуально для платформ кредитования и BNPL, розничных планов рассрочки, где накапливаются штрафы за просрочку, и интеграторов, подключающих графики погашения из основных банковских систем или ERP в исходящие напоминания.
Как это работает
- Триггер
Кредитование или выставление счетов обнаруживает приближение даты платежа и запускает событие напоминания.
event·triggered - Захват события
Система определяет номер телефона клиента и данные по кредиту.
phone:"+…" - Собрать и отправить
Персонализированное сообщение по шаблону создаётся с трёх переменных в теле и кнопки со статическим URL.
POST/sendTemplate - Доставлено
Клиент получает напоминание в WhatsApp и может оплатить с помощью кнопки.
delivered - Отслеживание статуса
Результат доставки фиксируется для дальнейшего учёта или выставления счетов.
status:"read"

Техническая реализация
Требования
- Ключ API 1MSG · Как получить API-ключ
- Аккаунт WhatsApp Business · Как подключить WABA
- Шаблон WhatsApp · Как утвердить шаблон WABA
- Согласие клиента на получение сообщений · Как управлять согласием клиентов
Примеры кода
#!/usr/bin/env bash
set -euo pipefail
# === Configuration (replace "___" placeholders) ===
API_BASE_URL="https://api.1msg.io" # production 1MSG API base URL
CHANNEL_ID="___" # channel ID from 1MSG dashboard
API_TOKEN="___" # channel JWT token (Bearer)
TEMPLATE_NAME="___" # approved template name
TEMPLATE_NAMESPACE="___" # template namespace (required — send fails without it)
TEMPLATE_LANGUAGE="___" # template language code, e.g. "en"
# === Test data ===
TEST_PHONE="___" # client phone in international format
TEST_CUSTOMERNAME="___" # {{1}} customer name
TEST_PAYMENTAMOUNT="___" # {{2}} payment amount
TEST_DUEDATE="___" # {{3}} due date
PHONE_NORM="$(printf '%s' "$TEST_PHONE" | tr -cd '0-9')"
for pair in "CHANNEL_ID=$CHANNEL_ID" "API_TOKEN=$API_TOKEN" \
"TEMPLATE_NAME=$TEMPLATE_NAME" "TEMPLATE_NAMESPACE=$TEMPLATE_NAMESPACE" \
"TEMPLATE_LANGUAGE=$TEMPLATE_LANGUAGE" "TEST_PHONE=$TEST_PHONE" \
"TEST_CUSTOMERNAME=$TEST_CUSTOMERNAME" \
"TEST_PAYMENTAMOUNT=$TEST_PAYMENTAMOUNT" \
"TEST_DUEDATE=$TEST_DUEDATE"; do
val="${pair#*=}"
if [ -z "$val" ] || [ "$val" = "___" ]; then
echo "Missing configuration value: ${pair%%=*}" >&2
exit 1
fi
done
if [ -z "$PHONE_NORM" ]; then
echo "Error: phone number has no digits after normalization" >&2
exit 1
fi
URL="${API_BASE_URL%/}/${CHANNEL_ID}/sendTemplate"
# params carries body ONLY. Button text is fixed in the Meta template — no button param.
# {{1}} customer name → ${TEST_CUSTOMERNAME}
# {{2}} payment amount → ${TEST_PAYMENTAMOUNT}
# {{3}} due date → ${TEST_DUEDATE}
read -r -d '' PAYLOAD <<JSON || true
{
"phone": "${PHONE_NORM}",
"template": "${TEMPLATE_NAME}",
"namespace": "${TEMPLATE_NAMESPACE}",
"language": { "policy": "deterministic", "code": "${TEMPLATE_LANGUAGE}" },
"params": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "${TEST_CUSTOMERNAME}" },
{ "type": "text", "text": "${TEST_PAYMENTAMOUNT}" },
{ "type": "text", "text": "${TEST_DUEDATE}" }
]
}
]
}
JSON
RESPONSE="$(curl -s -w '\n%{http_code}' -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_TOKEN}" \
-d "$PAYLOAD")"
HTTP_CODE="$(printf '%s' "$RESPONSE" | tail -n1)"
BODY="$(printf '%s' "$RESPONSE" | sed '$d')"
case "$BODY" in
*'"sent":true'*) ok=1 ;;
*) ok=0 ;;
esac
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ] && [ "$ok" -eq 1 ]; then
echo "Message sent to client."
echo "API response: $BODY"
else
echo "Send failed. HTTP status: $HTTP_CODE" >&2
echo "$BODY" >&2
exit 1
fi
«Статус ответа и доставки»
HTTP 2xx and JSON "sent": true mean 1MSG accepted the message for sending — not that it already reached the customer's phone. Save the id field (looks like wamid.…) to correlate delivery callbacks.
{
"sent": true,
"id": "wamid.HBgLMzgwNjM5...",
"message": "Message accepted for delivery"
}sentПринято для отправки — не ещё на телефоне клиента
idСохраните это; на этом ключе строятся обратные вызовы доставки и
hookInfo
Доставка приходит позже, как отдельный обратный вызов. Зарегистрируйте POST …/webhookвебхук, и 1MSG будет отправлять обновления статуса на ваш HTTPS-эндпоинт в верхнеуровневом hooks[]payload
{
"hooks": [
{
"id": "gBGGeSaGViBfAgnlzOSHEwK9O6F",
"type": "message",
"status": "sent",
"timestamp": "1654864094",
"recipient_id": "556123122026"
}
]
}statussent,delivered,read— или статус ошибки, если применимоidСоотносит callback с
id, возвращённым вызовом sendtimestampСекунды Unix в виде строки
Если вы предпочитаете не получать обратные вызовы, вместо этого опрашивайте GET {base}/{channel}/hookInfo?messageId=<id>. На практике доставка часто завершается за несколько секунд, но контракт API этого не гарантирует, поэтому не блокируйте выполнение, ожидая её.
Распространённые ошибки
| Статус | Ответ | Причина |
|---|---|---|
| 200 | Message was not sent: template is not defined | namespace, template or language missing from the request body. |
| 200 | template name (…) does not exist in <language> | The template is approved in a different language than the one requested. |
| 200 | Message was not sent: provide chatId, phone, bsuid, or username | No recipient the channel could resolve. |
| 403 | access denied | The token is wrong, or belongs to a different channel than the URL. |
| 429 | too many requests. please try later | The channel is over its send rate. |

