Notice: AULM is not the official WhatsApp Business Platform
AULM is built on an unofficial WhatsApp Web session layer. We are not affiliated with Meta or WhatsApp and we do not provide the WhatsApp Business API (WABA). Use the service only for legitimate, opt-in communication. Spam, bulk marketing, or unusual sending behaviour may result in your number being restricted by WhatsApp — we do not guarantee delivery, ban-immunity, or Meta approval. For official business use cases, follow Meta's WhatsApp Business API requirements.
Introduction
aulm is a WhatsApp messaging API. You connect a phone by scanning a QR code in the dashboard, then send messages and receive inbound events over HTTP. There is no Meta business verification, no template approval, no WABA.
Why aulm
- Refund on failure. Transient upstream failures refund your quota slot automatically.
- Traceable. Every response carries
x-request-id. Log it; quote it. - Honest rate limits. Send endpoints stamp
retry-afteron every 429. - Signed webhooks. Published retry ladder — delivery is our problem, not yours.
Quick Install
Paste this into your app builder to install AULM. Then add your keys from the dashboard.
Build this in Lovable Cloud: put the send helper and webhook receiver in Supabase edge functions, and store AULM_API_KEY / AULM_SESSION_ID / AULM_WEBHOOK_SECRET as Lovable secrets (never in the frontend).
Integrate the AULM WhatsApp API (https://api.aulm.io) into this app so I can send (and optionally receive) WhatsApp messages.
SECRETS — store these server-side only, never in client/browser code. I will paste the values after you set up the placeholders:
- AULM_API_KEY (format: alm_sk_live_xxxxxxxx… ; use alm_sk_test_… while testing)
- AULM_SESSION_ID (the WhatsApp number's session id from my aulm dashboard)
- AULM_WEBHOOK_SECRET (only needed if I receive inbound messages)
1) SEND A TEXT MESSAGE (server-side helper):
POST https://api.aulm.io/v1/messages/send-text
Headers: Authorization: Bearer ${AULM_API_KEY} | Content-Type: application/json
Body: { "session_id": "${AULM_SESSION_ID}", "to": "+<E164 number>", "body": "<text>" }
On success (201) you get { data: { message_id, status }, request_id }.
On error, read error.code and handle these especially:
429 rate_too_fast -> sending too fast; wait and retry (honor the Retry-After header)
429 warmup_daily_limit -> new number's daily cap reached; try later
422 recipient_not_on_whatsapp -> number isn't on WhatsApp; don't retry
403 recipient_opted_out -> recipient replied STOP; never message them again
409 session_not_ready -> the number isn't connected yet
Add a simple retry with backoff for 429/502/503/504 only.
2) SEND AN IMAGE (optional helper):
POST https://api.aulm.io/v1/messages/send-image
Body: { "session_id": "${AULM_SESSION_ID}", "to": "+<E164>", "image": "<https URL or data:image/...;base64,...>", "caption": "<optional>" }
3) RECEIVE INBOUND / STATUS (optional — only if I want to handle replies/delivery):
Create ONE public POST endpoint (e.g. /webhooks/aulm). For each request:
- Read the raw request body BYTES (do not re-serialize JSON before verifying).
- Read header x-aulm-signature = "t=<unix>,v1=<hex>".
- Compute HMAC_SHA256(AULM_WEBHOOK_SECRET, `${t}.${rawBody}`) and compare to v1 using a constant-time compare. Reject (401) on mismatch, or if t is older than ~5 minutes.
- Then branch on header x-aulm-event:
inbound_message -> body.message has { from, body, type, media_url, ... }
message_status -> body has { message_id, status } (sent|delivered|read|played|failed)
- Respond 200 quickly (do heavy work async). Use header x-aulm-delivery-id for idempotency (it can repeat on retries).
Then I'll paste this endpoint's URL into my aulm dashboard.
RULES:
- Never expose AULM_API_KEY to the browser; all aulm calls go through my backend.
- Use alm_sk_test_ keys while building (responses include "simulated": true) and switch to alm_sk_live_ for production.
- Phone numbers must be E.164 (e.g. +14155551234).Set up with AI
Developer AI assistants (Claude, ChatGPT, Cursor) work best when given a clear specification. Copy the prompt below into your assistant to get correct, production-ready aulm code for your project immediately.
You are helping me integrate the aulm WhatsApp API into my project...
ABOUT
aulm sends WhatsApp messages via an HTTP API. It is unofficial...
Authentication
Every request takes a Bearer token in the Authorization header. Keys are generated in the dashboard and match the regex ^alm_sk_(live|test)_[0-9a-f]{64}$.
- alm_sk_live_… — real sends. Billable. Counts against your plan quota.
- alm_sk_test_… — every call is simulated. The messaging service is never invoked, no WhatsApp message is sent, the response shape is identical to live with
simulated: trueandmessage_idshapedsim_<hex>. Test sends are never billed.
Keys may also be restricted to specific session IDs. Requests against a non-whitelisted session return 403 session_not_allowed.
Authorization: Bearer alm_sk_live_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefQuickstart — first message in under 60 seconds
Connect a session.
Go to the dashboard → Sessions → New session → scan the QR code with the WhatsApp app on the phone you want to send from. Wait until status is connected.
Grab the session ID and an API key.
Copy the session UUID from the dashboard. Generate an alm_sk_test_… key first — you can ship the same code with alm_sk_live_… later.
Send.
curl -X POST https://api.aulm.io/v1/messages/send-text \ -H "Authorization: Bearer alm_sk_test_..." \ -H "Content-Type: application/json" \ -d '{"session_id":"<SESSION_ID>","to":"+14155551234","body":"Hello!"}'Sessions are dashboard-only.
There is no public API endpoint to create a session, pair it via QR, or disconnect it. All session lifecycle is managed in the aulm dashboard. The API only operates on sessions that are already connected. GET /v1/sessions/{id}/status is read-only.
Base URL & versioning
All endpoints are served from a single base URL. The path is versioned with /v1/. Breaking changes get a new path prefix; additive changes (new optional fields, new response headers) ship under /v1/.
https://api.aulm.ioResponses & request IDs
All /v1/* responses are JSON. Success bodies use { data: ..., request_id: ... }; errors use { error: { code, message } }.
Every response — success or error — sets x-request-id. Log it on your side and quote it to support; we can find the exact send in one query.
Send endpoints additionally stamp x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset (ISO 8601), and retry-after on 429s and on upstream-overload 503s.
Platform headers to ignore
x-deployment-idInjected by the hosting edge on 2xx responses. Not part of the
aulm contract — ignore.
Reliability
When a send fails, two things matter: did it cost you a quota slot, and should you retry the same request. aulm publishes both answers explicitly.
Quota refund matrix
What happens to your reserved quota slot when a send fails. Transient = refunded and not billed. Client error = slot kept.
| Condition | Status | error.code | Refunded? | Billable? | Retry? |
|---|---|---|---|---|---|
| Upstream timeout | 504 | messaging_service_timeout | Yes | No | Yes, same request |
| Network / unreachable | 502 | messaging_service_unreachable | Yes | No | Yes, same request |
| Upstream 5xx | 502 | messaging_service_error | Yes | No | Yes, same request |
| Upstream auth failure | 502 | messaging_service_error | Yes | No | Yes (ops alerted) |
| Upstream 429 (overload) | 503 | service_unavailable | Yes | No | Yes, honor Retry-After |
| Recipient unreachable (upstream 404) | 404 | invalid_request | No | Yes | No — fix recipient |
| Session not ready (upstream 409/412/422) | 409 | session_not_ready | No | Yes | No — connect session first |
| Other client error (4xx) | 400 | invalid_request | No | Yes | No — fix request |
Webhook retries
After a failed attempt, the next attempt is scheduled per this table. Attempt count is 1-indexed and matches `x-aulm-attempt`. `Retry-After` from your response overrides the ladder (capped at 1h).
| Attempt just failed | Next retry |
|---|---|
| 1 | 1 minute |
| 2 | 5 minutes |
| 3 | 30 minutes |
| 4 | 2 hours |
| 5 | 12 hours |
| 6 | Dead-lettered |
- Default max attempts per delivery:
6. - Endpoint auto-disabled after
25consecutive failures (across deliveries). - Per-attempt HTTP timeout:
10s. - Target first-delivery latency:
~60s.
Errors
Errors always come back as { error: { code, message } }. The HTTP status tells you the broad class; the error.code tells you the exact cause. See the full error-code index at the bottom of this page.
{ "error": { "code": "session_not_ready", "message": "Session is not ready to send messages. Connect the session first." }}Send a text message
/v1/messages/send-textResponses
201Message accepted by the messaging service.400401402403404409429500502503504
curl -X POST https://api.aulm.io/v1/messages/send-text \ -H "Authorization: Bearer $AULM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab", "to": "+14155551234", "body": "Hello from aulm!"}'{ "data": { "message_id": "true_14155551234@c.us_3EB0...", "session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab", "to": "+14155551234", "status": "sent", "simulated": false }, "request_id": "9c8b7a6d-1234-4abc-def0-112233445566"}Send an image message
/v1/messages/send-imageResponses
201Image accepted by the messaging service.400401402403404409429500502503504
curl -X POST https://api.aulm.io/v1/messages/send-image \ -H "Authorization: Bearer $AULM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab", "to": "+14155551234", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...", "caption": "Receipt attached"}'{ "data": { "message_id": "true_14155551234@c.us_3EB0...", "session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab", "to": "+14155551234", "status": "sent", "simulated": false }, "request_id": "9c8b7a6d-1234-4abc-def0-112233445566"}Get session connection status
/v1/sessions/{id}/statusResponses
200Current session status.400401403404500
curl https://api.aulm.io/v1/sessions/<SESSION_ID>/status \ -H "Authorization: Bearer $AULM_API_KEY"{ "data": { "session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab", "status": "connected" }}Webhooks
Register a single HTTPS endpoint in the dashboard. aulm POSTs JSON events to it with a signed header, retries failures per the published ladder, and gives you a stable x-aulm-delivery-id for idempotency.
Request headers
| x-aulm-event | Event name: inbound_message or message_status. |
| x-aulm-delivery-id | UUID for this logical event. Use as your idempotency key — the same value may arrive across retries. |
| x-aulm-signature | t=<unix>,v1=<hex>. HMAC_SHA256(secret, `${t}.${rawBody}`). Verify over the raw bytes. |
| x-aulm-attempt | 1-indexed attempt number. |
Verifying the signature
Always verify over the raw request body bytes — do not re-serialize the JSON.
const crypto = require("crypto");
function verifyAulmSignature(rawBody, header, secret) {
// header looks like: "t=1716893742,v1=abc123..."
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=")),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
// timing-safe compare
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1 ?? "", "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Events
x-aulm-event: inbound_messageA WhatsApp message was received on a connected session.
Delivered to your registered webhook endpoint. The `x-aulm-event` request header equals `inbound_message`. Test deliveries triggered from the dashboard include `"test": true` at the top level of the body.
{ "event": "inbound_message", "delivery_id": "00000000-0000-4000-8000-000000000000", "timestamp": "2026-05-28T10:15:42.000Z", "session_id": "00000000-0000-4000-8000-000000000000", "test": false, "message": { "id": "string", "from": "string", "to": "string", "chat_id": "string", "type": "string", "body": "string", "is_group": false, "from_me": false, "media_url": "https://example.com/...", "received_at": "2026-05-28T10:15:42.000Z" }}x-aulm-event: message_statusDelivery status changed for a message you sent.
Delivered to your registered webhook endpoint. The `x-aulm-event` request header equals `message_status`. The `status` field is one of: `sent`, `delivered`, `read`, `played`, `failed`.
{ "event": "message_status", "delivery_id": "00000000-0000-4000-8000-000000000000", "timestamp": "2026-05-28T10:15:42.000Z", "session_id": "00000000-0000-4000-8000-000000000000", "message_id": "string", "status": "sent", "from": "string", "to": "string", "status_at": "2026-05-28T10:15:42.000Z"}Number warm-up & rate limits
To protect numbers from being banned by WhatsApp, two per-number limits apply to live sends (on top of your plan's daily quota). Test-mode keys (alm_sk_test_…) simulate sends and bypass these guards.
Send velocity
A number may send at most 12 messages per minute. Bursting above this returns 429 rate_too_fast with a Retry-After header. Pace your sends.
Warm-up ramp
A newly-connected number's daily send limit grows over its first two weeks, then your plan quota applies:
| Number age (since connected) | Max sends/day |
|---|---|
| Day 0–2 | 20 |
| Day 3–6 | 50 |
| Day 7–13 | 200 |
| Day 14+ | Your plan's daily limit |
Exceeding the warm-up cap returns 429 warmup_daily_limit. A new number on any plan is limited during warm-up regardless of plan size — this is intentional and protects deliverability.
Opt-out (STOP)
If a recipient replies STOP, UNSUBSCRIBE, CANCEL (or equivalent), they are added to your suppression list and further sends to them return 403 recipient_opted_out. This keeps block/report rates low.
Recipient validation
Before a live send, the recipient is checked against WhatsApp. Numbers that are valid E.164 format but not registered on WhatsApp return 422 recipient_not_on_whatsapp. Malformed phone numbers return 404 invalid_request. Both keep the reserved quota slot (no charge).
Plans & limits
Sessions per plan
| Plan | Active sessions | Message limit |
|---|---|---|
| trial | 1 | 20 total across a fixed 5-day window (not per day) |
| starter | 1 | 1000 / day (UTC) |
| pro | 2 | 10000 / day (UTC) |
| business | 6 | 100000 / day (UTC) |
Trial is a fixed 5-day window with a monotonic counter — it ends at whichever comes first: hitting the cap or 5 days from signup. Daily plans reset at next UTC midnight.
New numbers are additionally limited by the warm-up ramp for their first ~2 weeks, regardless of plan.
Error-code index
| Status | error.code | Meaning | Quota slot |
|---|---|---|---|
| 400 | invalid_json | Request body is not valid JSON. | — |
| 400 | invalid_request | Validation failed (missing field, bad phone, bad image). | — |
| 401 | missing_authorization | No Authorization header. | — |
| 401 | invalid_authorization | Authorization header isn't Bearer scheme. | — |
| 401 | invalid_api_key | Key does not match any active record. | — |
| 401 | api_key_revoked | Key was revoked or soft-deleted. | — |
| 401 | api_key_expired | Key passed its expiry date. | — |
| 401 | account_deactivated | Customer account is soft-deleted. | — |
| 402 | subscription_required | No active plan and trial is over. | — |
| 403 | session_not_allowed | API key is restricted from this session. | — |
| 403 | recipient_opted_out | Recipient replied STOP/unsubscribe and is suppressed for this customer (live sends only). | Kept |
| 404 | session_not_found | Session UUID doesn't exist for this account. | — |
| 404 | invalid_request | Malformed/invalid recipient (bad format). | Kept |
| 409 | session_not_ready | Session exists but is not connected. | Kept |
| 422 | recipient_not_on_whatsapp | Recipient number is valid but is not registered on WhatsApp (live sends only). | Kept |
| 429 | trial_quota_exceeded | Hit your trial cap. See x-ratelimit-reset. | — |
| 429 | daily_quota_exceeded | Hit your daily cap. Resets at next UTC midnight. | — |
| 429 | rate_too_fast | Per-number send velocity exceeded (12/min). Honor Retry-After (live sends only). | Kept |
| 429 | warmup_daily_limit | New number's warm-up daily cap reached (ramps over ~14 days, live sends only). | Kept |
| 500 | internal_error | Server-side failure. Quote x-request-id to support. | — |
| 502 | messaging_service_unreachable | Network failure to upstream. | Refunded |
| 502 | messaging_service_error | Upstream returned 5xx or auth-failed. | Refunded |
| 503 | service_unavailable | Upstream overload. Honor Retry-After. | Refunded |
| 504 | messaging_service_timeout | Upstream did not respond in time. | Refunded |