WhatsApp-бот
Людина пише боту свій знак, наприклад leo, і отримує гороскоп на сьогодні. На відміну від Telegram, WhatsApp Cloud API сам надсилає повідомлення на ваш вебхук, тому серверу потрібна публічна адреса з HTTPS.
1. Що знадобиться
Section titled “1. Що знадобиться”- Застосунок на developers.facebook.com типу Business з доданим продуктом WhatsApp. На сторінці API Setup є тестовий номер, його Phone number ID і тимчасовий токен доступу; для роботи довше доби створіть постійний токен системного користувача.
- App secret з App settings → Basic: ним Meta підписує кожне повідомлення на вебхук.
- Ключ AstroWay Тільки сервер з областю
public/horoscope/*, як описано в розділі Ключ для автоматизації. Ендпоінт кредитів не витрачає.
export ASTROWAY_API_KEY="aw_live_..."export WHATSAPP_TOKEN="EAAG..."export WHATSAPP_PHONE_NUMBER_ID="1234567890"export WHATSAPP_APP_SECRET="..."export WHATSAPP_VERIFY_TOKEN="будь-який-рядок-який-ви-вигадаєте"2. Код
Section titled “2. Код”Збережіть як whatsapp.mjs:
// Node 22 or newer, no dependencies.import { createServer } from 'node:http';import { createHmac, timingSafeEqual } from 'node:crypto';
const GRAPH = 'https://graph.facebook.com/v23.0';const ASTROWAY = 'https://api.astroway.info/v1';const LANG = 'uk';// One text per sign per day: at most 12 calls a day, whatever the traffic.const cache = new Map();const SIGNS = ['aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo', 'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces'];
async function answer(text) { const sign = text.trim().toLowerCase(); if (!SIGNS.includes(sign)) return `Send your sign, for example: leo\n${SIGNS.join(', ')}`; const key = `${new Date().toISOString().slice(0, 10)} ${sign}`; if (!cache.has(key)) { const res = await fetch(`${ASTROWAY}/public/horoscope/daily?sign=${sign}&lang=${LANG}`, { headers: { 'X-Api-Key': process.env.ASTROWAY_API_KEY }, }); const json = await res.json(); if (!json.ok) return `AstroWay: ${json.error.message}`; cache.set(key, `${json.data.date}, ${sign}\n\n${json.data.horoscope}`.slice(0, 4000)); } return cache.get(key);}
function signedByMeta(raw, header) { const expected = 'sha256=' + createHmac('sha256', process.env.WHATSAPP_APP_SECRET).update(raw).digest('hex'); return header?.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));}
async function send(to, body) { await fetch(`${GRAPH}/${process.env.WHATSAPP_PHONE_NUMBER_ID}/messages`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.WHATSAPP_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ messaging_product: 'whatsapp', to, type: 'text', text: { body } }), });}
createServer(async (req, res) => { const url = new URL(req.url, 'http://localhost');
// Meta checks the webhook once, when you save its URL. if (req.method === 'GET') { const ok = url.searchParams.get('hub.mode') === 'subscribe' && url.searchParams.get('hub.verify_token') === process.env.WHATSAPP_VERIFY_TOKEN; res.writeHead(ok ? 200 : 403).end(ok ? url.searchParams.get('hub.challenge') : ''); return; }
const chunks = []; for await (const chunk of req) chunks.push(chunk); const raw = Buffer.concat(chunks); if (!signedByMeta(raw, req.headers['x-hub-signature-256'])) { res.writeHead(401).end(); return; } // Answer Meta first: a slow 200 makes it deliver the same message again. res.writeHead(200).end();
const messages = JSON.parse(raw).entry?.[0]?.changes?.[0]?.value?.messages ?? []; for (const message of messages) { if (message.type !== 'text') continue; await send(message.from, await answer(message.text.body)); }}).listen(process.env.PORT ?? 3000);Запустіть node whatsapp.mjs за HTTPS-проксі або на хостингу, який дає публічну адресу.
3. Підключіть вебхук
Section titled “3. Підключіть вебхук”- У застосунку відкрийте WhatsApp → Configuration → Webhook → Edit.
- Callback URL: адреса вашого сервера, наприклад
https://bot.example.com/webhook. Verify token: те саме значення, що вWHATSAPP_VERIFY_TOKEN. Meta одразу надішлеGETзhub.challenge, і код поверне його назад. - У Webhook fields підпишіться на
messages. - Напишіть
leoна тестовий номер із телефону, доданого в список отримувачів на сторінці API Setup.
Що варто знати
Section titled “Що варто знати”- Підпис обов’язковий. Адреса вебхука публічна, тож без перевірки
X-Hub-Signature-256будь-хто міг би змусити бота писати людям. Запит без правильного підпису отримує401. - Вільний текст лише у відповідь. Cloud API дозволяє довільне повідомлення протягом 24 годин після останнього повідомлення людини. Розсилка гороскопу щоранку тим, хто давно не писав, потребує затвердженого шаблону повідомлення.
- Кеш у коді тримає не більше за 12 запитів до API на добу. Ключ Free на
/v1/public/*рахується в той самий ліміт, що й запит без ключа, 30 запитів на годину з IP вашого сервера, а неправильний ключ мовчки дає саме цей анонімний ліміт замість401. - Версія Graph API у
GRAPHце будь-яка чинна версія. Meta підтримує кожну щонайменше два роки, тож колиv23.0застаріє, змініть рядок.