Get started
Quickstart
Make your first authenticated request, connect a customer and send a template message.
In a nutshell
Register your customer as a business, send their admin to the connect link, then send an approved template with a reference. Everything else arrives as callbacks.
Before you start
You need a product token from a PaalChat operator. It is shown once - put it
in your server's secret store, for example PAALCHAT_TOKEN.
Warning
Never use the token in a browser, mobile app or public repository. All calls to PaalChat must come from your server.
1. Check the token
curl https://whatsapp.paaltech.org/api/v1/me \
-H "Authorization: Bearer $PAALCHAT_TOKEN" \
-H "Accept: application/json"
$me = Http::withToken(config('services.paalchat.token'))
->acceptJson()
->get('https://whatsapp.paaltech.org/api/v1/me')
->throw()
->json('data');
const res = await fetch('https://whatsapp.paaltech.org/api/v1/me', {
headers: { Authorization: `Bearer ${process.env.PAALCHAT_TOKEN}`, Accept: 'application/json' },
});
const { data: me } = await res.json();
import os, requests
me = requests.get(
"https://whatsapp.paaltech.org/api/v1/me",
headers={"Authorization": f"Bearer {os.environ['PAALCHAT_TOKEN']}", "Accept": "application/json"},
).json()["data"]
The response names your product and the token's abilities:
{"data": {"product": "multi-skuul", "name": "Multi-SKUUL",
"token": {"name": "production", "abilities": ["businesses.read", "businesses.write", "connections.read", "connections.manage", "templates.read", "messages.read", "messages.send"], "expires_at": null}}}
2. Register a customer as a business
Use your own ID for the customer as external_id. Calling it again updates the business.
curl -X POST https://whatsapp.paaltech.org/api/v1/businesses \
-H "Authorization: Bearer $PAALCHAT_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"external_id": "presec", "name": "Presec Legon", "email": "info@presec.edu.gh"}'
Http::withToken(config('services.paalchat.token'))->acceptJson()
->post('https://whatsapp.paaltech.org/api/v1/businesses', [
'external_id' => 'presec',
'name' => 'Presec Legon',
'email' => 'info@presec.edu.gh',
])->throw();
await fetch('https://whatsapp.paaltech.org/api/v1/businesses', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.PAALCHAT_TOKEN}`, Accept: 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ external_id: 'presec', name: 'Presec Legon', email: 'info@presec.edu.gh' }),
});
requests.post(
"https://whatsapp.paaltech.org/api/v1/businesses",
headers={"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"},
json={"external_id": "presec", "name": "Presec Legon", "email": "info@presec.edu.gh"},
).raise_for_status()
3. Connect their WhatsApp
Ask for a one-time link and redirect the customer's administrator to it.
curl -X POST https://whatsapp.paaltech.org/api/v1/businesses/presec/whatsapp/connect \
-H "Authorization: Bearer $PAALCHAT_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"return_url": "https://presec.skuuls.paaltech.org/settings/whatsapp"}'
$link = Http::withToken(config('services.paalchat.token'))->acceptJson()
->post('https://whatsapp.paaltech.org/api/v1/businesses/presec/whatsapp/connect', [
'return_url' => route('settings.whatsapp'),
])->throw()->json('data');
return redirect()->away($link['url']);
const { data: link } = await (await fetch(`${BASE}/businesses/presec/whatsapp/connect`, {
method: 'POST', headers: HEADERS,
body: JSON.stringify({ return_url: 'https://presec.skuuls.paaltech.org/settings/whatsapp' }),
})).json();
res.redirect(link.url);
link = requests.post(f"{BASE}/businesses/presec/whatsapp/connect", headers=HEADERS,
json={"return_url": "https://presec.skuuls.paaltech.org/settings/whatsapp"}).json()["data"]
return redirect(link["url"])
The admin logs in to Meta on PaalChat's page and picks a number. You receive a
whatsapp.connection callback with "event": "connected". See Connect WhatsApp.
4. Send a template
Business-initiated messages must use an approved template. Always send your own reference.
curl -X POST https://whatsapp.paaltech.org/api/v1/businesses/presec/messages \
-H "Authorization: Bearer $PAALCHAT_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{
"to": "+233241234567",
"type": "template",
"reference": "skuul-msg-8812",
"template": {"name": "fees_reminder", "language": "en_US",
"components": [{"type": "body", "parameters": [
{"type": "text", "text": "Mrs Mensah"}, {"type": "text", "text": "Kofi"},
{"type": "text", "text": "450.00"}, {"type": "text", "text": "30 October"}]}]}
}'
$message = Http::withToken(config('services.paalchat.token'))->acceptJson()
->post('https://whatsapp.paaltech.org/api/v1/businesses/presec/messages', [
'to' => '+233241234567',
'type' => 'template',
'reference' => 'skuul-msg-'.$notification->id,
'template' => [
'name' => 'fees_reminder',
'language' => 'en_US',
'components' => [['type' => 'body', 'parameters' => [
['type' => 'text', 'text' => 'Mrs Mensah'], ['type' => 'text', 'text' => 'Kofi'],
['type' => 'text', 'text' => '450.00'], ['type' => 'text', 'text' => '30 October'],
]]],
],
])->throw()->json('data');
const { data: message } = await (await fetch(`${BASE}/businesses/presec/messages`, {
method: 'POST', headers: HEADERS,
body: JSON.stringify({
to: '+233241234567', type: 'template', reference: `skuul-msg-${notification.id}`,
template: { name: 'fees_reminder', language: 'en_US', components: [{ type: 'body', parameters: [
{ type: 'text', text: 'Mrs Mensah' }, { type: 'text', text: 'Kofi' },
{ type: 'text', text: '450.00' }, { type: 'text', text: '30 October' }] }] },
}),
})).json();
message = requests.post(f"{BASE}/businesses/presec/messages", headers=HEADERS, json={
"to": "+233241234567", "type": "template", "reference": f"skuul-msg-{notification.id}",
"template": {"name": "fees_reminder", "language": "en_US", "components": [{"type": "body", "parameters": [
{"type": "text", "text": "Mrs Mensah"}, {"type": "text", "text": "Kofi"},
{"type": "text", "text": "450.00"}, {"type": "text", "text": "30 October"}]}]},
}).json()["data"]
202 Accepted means the message is queued. 200 OK means that reference was
already sent - you get the original message back and nothing is sent twice.
5. Receive callbacks
A PaalChat operator registers your callback URL and gives you a signing secret.
You will receive message.status as the message is sent, delivered and read, and
message.received when the parent replies. Verify every callback - see Callbacks.
Next steps
- Sending messages in depth, including free-form text and the 24-hour window.
- Receiving messages to build an inbox.
- Safe retries so network failures never send twice.