Build on BotCrafter
One API key lets any app you run — a clinic system, a booking tool, a POS — send WhatsApp messages, send approved templates, and build bots on behalf of a BotCrafter account. One webhook gets every reply sent straight back to you. This page covers both sides.
https://api.botcrafter.inThis is the live production API. Every path below is relative to it.- Create an accountSign up for BotCrafter with a business name, email, and password. This becomes the account everything below acts on — every message sent and every flow built through the API belongs to this account, exactly as if you'd done it from the dashboard yourself.
- Generate an API keyFrom Settings → API key, generate a key. It's shown once — store it somewhere safe. Regenerating immediately invalidates the previous key, so treat rotation as a real cutover, not a casual refresh.
- Connect a WhatsApp numberFrom Connections → WhatsApp, connect the number you'll send from. This gives you a connection_id — every send call below needs it, since an account can have more than one number connected.
Core concepts
Authentication
Every request — to the API or in place of a dashboard login — is authenticated the same way: a bearer token in the Authorization header.
Authorization: Bearer botcrafter_••••••••••••••••••••••••••••••••There's one active key per account. It carries the full authority of the account owner — sending, publishing bots, everything on this page — so hand it only to apps you trust, and store it the way you'd store a database password, not a client-side config value.
Core concepts
Response format
Every response, success or failure, is the same envelope.
{
"success": true,
"data": { "sent": true }
}{
"success": false,
"error": "invalid API key"
}Check success before touching data — don't infer failure from an empty body.
Core concepts
The 24-hour window
This is a WhatsApp platform rule, not a BotCrafter one — it governs which of the two send endpoints below you can use.
WhatsApp only allows free-form messages within 24 hours of the customer's last message to you. Outside that window, the only way to reach them is a pre-approved template — a fixed message shape Meta has reviewed in advance. Sending a template is also how you re-open the window: once a customer replies, you can send free-form again for another 24 hours.
| Inside the 24h window | Outside the 24h window | |
|---|---|---|
| Free-form text Send a message | Works | Rejected by Meta |
| Approved template Send a template | Works | Works |
API reference
Sending & building
Four things an integration typically does: send a message, send a template, and build or publish a bot flow.
POST/api/v1/notify
Sends a free-form text message through a connected number.
Only works within 24 hours of the customer's last inbound message. See The 24-hour window.
Body
| Field | Type | |
|---|---|---|
connection_id | string | Required |
to | string, E.164 | Required |
message | string | Required |
# cURL
curl -X POST https://api.botcrafter.in/api/v1/notify \
-H "Authorization: Bearer $BOTCRAFTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connection_id": "8e2c...f01a",
"to": "+15551234567",
"message": "Hi Jane — your 3pm appointment is confirmed."
}'POST/api/v1/integrations/templates/:id/send
Sends an approved template — the way to reach someone outside the 24h window, or to guarantee delivery regardless of when they last wrote in.
The template's status must be approved. Templates are created and submitted for Meta's review from the dashboard; check status with List templates first.
Body
| Field | Type | |
|---|---|---|
connection_id | string | Required |
to | string, E.164 | Required |
parameters | string[] | Optional |
card_parameters | string[][] | Optional, carousels only |
# cURL — parameters fill the template's {{1}}, {{2}}... in order
curl -X POST https://api.botcrafter.in/api/v1/integrations/templates/tmpl_9f21/send \
-H "Authorization: Bearer $BOTCRAFTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connection_id": "8e2c...f01a",
"to": "+15551234567",
"parameters": ["Jane", "Friday 3:00 PM"]
}'GET/api/v1/integrations/templates
Lists every template on the account, so you can find an approved one's id before sending it.
{
"success": true,
"data": [
{
"id": "tmpl_9f21",
"name": "appointment_reminder",
"language": "en_US",
"category": "UTILITY",
"status": "approved"
}
]
}status is one of draft pending approved rejected — only approved can be sent.
Build & publish a flow
A bot flow is built the same way whether it comes from the visual builder or from your own app: create it, shape its graph, point it at a number, publish it. These five calls are that lifecycle.
Creates an empty flow.
curl -X POST $BASE_URL/api/v1/integrations/flows \
-H "Authorization: Bearer $BOTCRAFTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Appointment reminder" }'
# → { "success": true, "data": { "id": "flow_c81a" } }Lists every flow on the account — id, name, status, and which number it's assigned to.
Fetches one flow, including its full graph — the node/edge shape the builder produces.
Replaces the flow's name and graph.
curl -X PUT $BASE_URL/api/v1/integrations/flows/flow_c81a \
-H "Authorization: Bearer $BOTCRAFTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Appointment reminder",
"graph": {
"nodes": [
{ "id": "n1", "type": "trigger", "position": {"x":0,"y":0}, "data": {} },
{ "id": "n2", "type": "message", "position": {"x":0,"y":160},
"data": { "text": "Hi {{contact.name}}, this is a reminder about your visit." } }
],
"edges": [ { "id": "e1", "source": "n1", "target": "n2" } ]
}
}'Assigns which connected number the flow runs on.
curl -X PUT $BASE_URL/api/v1/integrations/flows/flow_c81a/connection \
-H "Authorization: Bearer $BOTCRAFTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "connection_id": "8e2c...f01a" }'Publishes a draft flow — it starts running for real customers on its assigned number.
Publish after the graph is built and a connection is assigned — an unpublished flow never runs, no matter how it was created.
Webhooks
Receiving messages
There's no "check for new messages" endpoint to poll — instead, your app gets every inbound WhatsApp event pushed to it the moment it happens.
- Pick a URL on your own appSomething like https://your-app.example.com/webhooks/botcrafter that accepts a POST with a JSON body. It needs to be publicly reachable — BotCrafter has to be able to call it.
- Register it in the dashboardUnder Settings → Connections → Your own backend, set that URL and choose a secret (min. 8 characters — you invent this, it isn't issued to you). Use Test to confirm delivery works before relying on it.
- Verify and handle deliveriesEvery delivery carries an HMAC signature made with that same secret — see Verifying signatures below.
Delivery
| Field | |
|---|---|
POST to your target URL | |
Content-Type: application/json | |
X-Wabot-Signature: sha256=<hex> | HMAC-SHA256 of the raw body |
{
"event": "message.received",
"timestamp": "2026-08-22T10:04:11Z",
"data": {
"connection_id": "8e2c...f01a",
"from": "+15551234567",
"contact_name": "Jane Doe",
"type": "text",
"body": "Can I reschedule to Monday?",
"media_url": ""
}
}Respond with any 2xx quickly. A non-2xx or a timeout is logged as a failed delivery — there's no retry queue yet, so don't do slow work before responding; queue it and respond first.
Verifying signatures
Same convention as Stripe and GitHub: recompute the HMAC yourself and compare.
const crypto = require('crypto');
function isValid(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody) // the raw, unparsed request body
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(signatureHeader)
);
}Verify against the raw request body — parsing to JSON and re-serializing before hashing will produce a mismatch.
Event types
| Event | Fires when |
|---|---|
message.received | A customer sends any inbound message on a connected number. |
More event types (delivery receipts, flow-triggered events) are on the roadmap — the envelope shape above is designed so handling a new one is just a new event switch case, not a new integration.
Reference
Errors
Status codes you'll actually see from these endpoints.
error carries their message verbatim, e.g. a send outside the 24h window with no template.No rate limit is enforced today — that's a trust extended to you, not a guarantee for the future. Build as if reasonable throttling could show up later.
