Developer Documentation
Send WhatsApp messages at scale. Connect numbers, manage contacts, blast personalised messages, build drip sequences, automate enrollments, and track delivery — all through one clean REST API.
Introduction
Wamator is a multi-tenant WhatsApp messaging API. You register as a vendor, subscribe to a plan, and use your API key to manage sub-users, phone numbers, contacts, and messages.
x-api-key header. All IDs are UUIDs.| Term | Who they are |
|---|---|
Vendor | Your application (e.g. Edumess). One API key, one account, owns everything below. |
Sub-user | Your end customers (e.g. a school owner). Registered by you via API. |
Phone Number | A WhatsApp number a sub-user connects by scanning a QR code. |
Contact | A message recipient. Stored per sub-user in the CRM. |
Authentication
Every protected endpoint requires your vendor API key in the x-api-key request header. You get this key from your Wamator dashboard — not via an API call.
x-api-key: your_api_key_hereError Codes
| Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
400 | Bad request — check your body fields |
401 | Invalid or missing API key |
403 | Plan limit reached |
404 | Resource not found |
409 | Already exists |
502 | WhatsApp gateway error |
{ "detail": "No active subscription" }Integration Flow
From zero to sending your first WhatsApp message in 7 steps.
x-api-key headerPostman Setup
The fastest way to explore and test every endpoint. Import the collection and you have all 60+ requests pre-built with example bodies and headers — no manual setup.
Two ways to get it into Postman:
https://api.wamator.com/assets/wamator-postman-collection.json
After importing, click the Wamator API collection in the Postman sidebar → open the Variables tab → fill in these values:
| Variable | Value | How to get it |
|---|---|---|
base_url | https://api.wamator.com | Already pre-filled — leave as is |
api_key | Your vendor API key | Run Register vendor or Login — copy the api_key from the response |
user_id | A sub-user UUID | Run Register user — copy the id |
number_id | A phone number UUID | Run Add number — copy the id |
contact_id | A contact UUID | Run Bulk import contacts — copy any returned id |
list_id | A list UUID | Run Create list — copy the id |
sequence_id | A sequence UUID | Run Create sequence — copy the id |
Follow this order on your first session — each step gives you IDs you need for the next:
api_key → paste into the api_key collection variableid → paste into the user_id variableid → paste into the number_id variableactive once connected{{api_key}}, {{user_id}}, etc. Once you set a variable in Step 2, all requests that need it update automatically.Vendors
Your top-level account. Sign up at app.wamator.com, verify your email, then retrieve your API key from the Settings page. The endpoints below are for managing your profile after signup.
{
"email": "admin@edumess.com",
"password": "YourPassword123!"
}
{
"status": "ok",
"vendor": {
"id": "7a689d72-...",
"name": "Edumess",
"email": "admin@edumess.com",
"api_key": "c25325739d4bb..."
}
}// Send only the fields you want to change
{
"name": "Edumess Africa",
"phone_number": "2348099999999"
}
{ "status": "ok", "message": "Profile updated" }Plans
Plans define vendor limits — phone numbers, contacts, and messages per billing period. No auth required to list.
{
"status": "ok",
"plans": [
{
"id": "a741616a-9725-4737-b67c-59811277685e",
"name": "Starter Plan",
"max_phone_numbers": 3,
"max_contacts": 1000,
"max_messages": 1000,
"duration_days": 30,
"features": { "automation": false, "analytics": false },
"prices": {
"NG": { "price": 5000.00, "currency": "NGN" },
"US": { "price": 9.99, "currency": "USD" }
}
}
]
}Subscriptions
Subscribe your vendor to a plan. All limits are enforced at the vendor level. Subscribing to a new plan cancels the current one.
// Get plan_id from GET /plans/
{
"plan_id": "a741616a-9725-4737-b67c-59811277685e"
}
{
"status": "ok",
"message": "Subscribed to Starter Plan",
"subscription_id": "da40b398-...",
"ends_at": "2026-08-01T19:49:04+00:00"
}{
"status": "ok",
"subscription": {
"plan_name": "Starter Plan",
"max_contacts": 1000,
"max_messages": 1000,
"max_phone_numbers": 3,
"contacts_used": 240,
"messages_sent": 85,
"ends_at": "2026-08-01T19:49:04+00:00",
"status": "active"
}
}Sub-Users
Your end customers. In Edumess, each school owner is a sub-user. Register them when they sign up or subscribe to messaging in your app.
external_id to link a Wamator sub-user to your own DB record. You won't need to store the Wamator UUID separately.{
"name": "Funcare School",
"company_name": "Funcare International",
"email": "admin@funcare.com",
"external_id": "school_001"
}
{
"status": "ok",
"message": "User registered successfully",
"id": "702cc6ac-8832-447c-8b7a-270ed1841a6b"
}user_id in numbers, contacts, and message requests.{
"user_id": "702cc6ac-...",
"has_active_subscription": true,
"status": "active",
"plan_name": "Basic School Plan",
"plan_config_id": "b4e9a271-...",
"billing_period": "monthly",
"started_at": "2026-07-08T10:00:00+00:00",
"ends_at": "2026-08-08T10:00:00+00:00",
"messages_sent": 1240,
"max_messages": 5000,
"messages_remaining": 3760,
"contacts_used": 85,
"max_contacts": 500,
"contacts_remaining": 415,
"selling_price": 15000,
"currency": "NGN"
}{
"user_id": "702cc6ac-...",
"has_active_subscription": false,
"status": "inactive",
"plan_name": null,
"plan_config_id": null,
"billing_period": null,
"started_at": null,
"ends_at": null,
"messages_sent": 0,
"max_messages": 0,
"messages_remaining": null,
"contacts_used": 0,
"max_contacts": 0,
"contacts_remaining": null
}// Only send fields to change
{
"name": "Funcare Academy",
"email": "info@funcare.com"
}
{ "status": "ok", "message": "User updated" }WhatsApp Numbers
Each sub-user connects a WhatsApp number by scanning a QR code. Once connected, messages are sent from that number.
{
"user_id": "702cc6ac-8832-447c-8b7a-270ed1841a6b",
"phone_number": "2349063570004",
"label": "Funcare Main Line"
}
{
"status": "ok",
"message": "Number added",
"id": "f3a1bc90-7e2d-4a11-9c33-112233445566"
}{
"status": "ok",
"session_id": "wa_f3a1bc90c2d1",
"qr": {
"qr": "data:image/png;base64,iVBORw0KGgo..."
}
}// JavaScript
document.getElementById('qr-img').src = response.qr.qr;
// PHP
echo '<img src="' . $response['qr']['qr'] . '" />';
// HTML element
<img id="qr-img" alt="Scan to connect WhatsApp" />
GET /numbers/{id}/status every 3 seconds after showing QR to detect when the sub-user scans it.// Connected
{ "status": "ok", "connected": true, "session_status": "CONNECTED" }
// Not yet scanned
{ "status": "ok", "connected": false, "session_status": "QR_GENERATED" }// Enabled
{ "warmup_enabled": true }
// Disabled — includes warning
{
"warmup_enabled": false,
"warning": "Disabling warm-up on a new number increases ban risk"
}{
"number_id": "f3a1bc90-...",
"phone_number": "2349063570004",
"health": "healthy",
"is_active": true,
"age_days": 12,
"queue_depth": 45,
"dead_last_hour": 0,
"warmup": {
"enabled": true,
"sends_today": 38,
"daily_limit": 150
}
}health is unhealthy (10+ dead sends in 1 hour), the number is auto-paused. Reconnect it via /numbers/{id}/connect.Contacts
Recipients of your messages. Each contact belongs to a sub-user. Store rich CRM fields and use them as {{placeholder}} in message templates.
{
"contacts": [
{
"user_id": "702cc6ac-8832-447c-8b7a-270ed1841a6b",
"phone_number": "2348012345678",
"name": "Mrs. Adaeze Obi",
"email": "adaeze@gmail.com",
"city": "Lagos",
"gender": "female",
"customer_tier": "gold",
"message_opt_in": true
}
]
}
{
"status": "ok",
"inserted": 2,
"skipped": 0,
"contact_ids": [
"ccc33333-0000-0000-0000-000000000001",
"ddd44444-0000-0000-0000-000000000002"
]
}POST /lists/{list_id}/contacts.| Field | Type | Notes |
|---|---|---|
phone_number | string | Required |
user_id | UUID | Required |
name | string | Use in {{name}} |
nickname | string | Use in {{nickname}} |
email | string | |
gender | string | male / female |
city | string | Use in {{city}} |
state | string | |
country | string | |
job_title | string | |
company_name | string | Use in {{company_name}} |
customer_tier | string | bronze / silver / gold / platinum / vip |
message_opt_in | boolean | Default true. False = excluded from blasts. |
{
"updates": {
"customer_tier": "vip",
"city": "Abuja",
"message_opt_in": false
}
}
{ "status": "ok", "message": "Contact updated" }Messages
Send WhatsApp messages — text blasts, single messages, media, interactive buttons, lists, location, contacts, and reactions.
{{name}}, {{city}}, {{nickname}}, {{company_name}}, {{customer_tier}} in templates — replaced per-contact before sending.{
"user_id": "702cc6ac-8832-447c-8b7a-270ed1841a6b",
"phone_number_id": "f3a1bc90-7e2d-4a11-9c33-112233445566",
"template": "Hello {{name}}, fees for this term are due Friday. Please ensure payment is made. Thank you - Funcare School."
}
{
"user_id": "702cc6ac-...",
"phone_number_id": "f3a1bc90-...",
"template": "Dear {{name}}, your {{customer_tier}} offer expires soon!",
"contact_ids": [
"aaa11111-0000-0000-0000-000000000001",
"bbb22222-0000-0000-0000-000000000002"
]
}
{
"status": "ok",
"batch_id": "e91f3c20-4a11-4b22-8c33-112233445566",
"sent": 240
}GET /messages/logs?batch_id=...{
"phone_number_id": "f3a1bc90-7e2d-4a11-9c33-112233445566",
"to": "2348012345678",
"text": "Hello! Your invoice is ready."
}
{ "status": "ok", "result": { ... } }{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"url": "https://example.com/image.jpg",
"caption": "Check this out!"
}
{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"url": "https://example.com/video.mp4",
"caption": "Watch this!"
}
{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"url": "https://example.com/audio.mp3"
}
{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"url": "https://example.com/invoice.pdf",
"filename": "invoice.pdf"
}
{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"body": "Please choose an option:",
"buttons": [
{ "id": "btn1", "text": "Pay Now" },
{ "id": "btn2", "text": "Remind Me Later" }
],
"footer": "Funcare School"
}
{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"title": "Our Services",
"body": "Choose a service:",
"button_text": "View Options",
"sections": [
{
"title": "Payments",
"rows": [
{ "id": "pay_fees", "title": "Pay School Fees", "description": "Pay for current term" },
{ "id": "pay_bus", "title": "Pay Bus Fees" }
]
}
],
"footer": "Funcare School"
}
{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"latitude": 6.5244,
"longitude": 3.3792,
"name": "Funcare School",
"address": "12 School Road, Lagos"
}
{
"phone_number_id": "f3a1bc90-...",
"to": "2348012345678",
"vcard": "BEGIN:VCARD\nVERSION:3.0\nFN:Funcare School\nTEL:+2349063570004\nEND:VCARD"
}
{
"phone_number_id": "f3a1bc90-...",
"message_id": "ABCD1234567",
"emoji": "👍"
}
{
"phone_number_id": "f3a1bc90-...",
"phone": "2348012345678"
}
{
"status": "ok",
"result": {
"id": "2348012345678@c.us",
"isWAContact": true
}
}{
"status": "ok",
"total": 240,
"logs": [
{
"recipient": "2348012345678",
"status": "delivered",
"sent_at": "2026-07-02T20:00:01+00:00",
"delivered_at": "2026-07-02T20:00:03+00:00"
}
]
}| Status | Meaning |
|---|---|
pending | Queued, not yet sent |
sent | Dispatched to WhatsApp |
delivered | Received on device (double tick ✓✓) |
failed | Failed — see error_message |
Groups
Create and manage WhatsApp groups. Send messages and media to an entire group at once.
{
"phone_number_id": "f3a1bc90-...",
"name": "Funcare Parents 2024",
"participants": [
"2348012345678",
"2348087654321"
]
}
{
"status": "ok",
"group": {
"id": "120363XXXXXXXXXX@g.us",
"name": "Funcare Parents 2024"
}
}@g.us) — use it to send messages to the group.{
"phone_number_id": "f3a1bc90-...",
"group_id": "120363XXXXXXXXXX@g.us",
"text": "Good morning parents! School resumes Monday."
}
{
"phone_number_id": "f3a1bc90-...",
"group_id": "120363XXXXXXXXXX@g.us",
"url": "https://example.com/notice.jpg",
"caption": "Term 3 timetable"
}
{
"phone_number_id": "f3a1bc90-...",
"participants": ["2348099999999", "2348011111111"]
}
{
"phone_number_id": "f3a1bc90-...",
"participants": ["2348099999999"]
}
Lists
User-level segmentation containers. A contact can belong to multiple lists. Use lists to target broadcasts and trigger sequences — e.g. "School Owners", "Crusade Attendees", "VIP Customers".
user_id when creating a list.{
"user_id": "702cc6ac-8832-447c-8b7a-270ed1841a6b",
"name": "School Owners",
"description": "All registered school owners on Edumess"
}
{
"id": "a1b2c3d4-...",
"name": "School Owners",
"description": "All registered school owners on Edumess",
"created_at": "2026-07-03T10:00:00Z"
}[
{
"id": "a1b2c3d4-...",
"name": "School Owners",
"description": "...",
"contact_count": 240,
"created_at": "2026-07-03T10:00:00Z"
}
]{
"contact_ids": [
"ccc33333-0000-0000-0000-000000000001",
"ddd44444-0000-0000-0000-000000000002"
]
}
{ "added": 2, "list_id": "a1b2c3d4-..." }Sequences
Named automation workflows with ordered steps. A sequence defines what messages to send, when to wait, and what conditions to branch on. You enroll contacts into sequences separately.
| Type | Config fields | What it does |
|---|---|---|
send_text | body | Sends a text message. Supports {{first_name}}, {{name}}, {{phone}}, {{email}} |
send_media | url, caption, media_type | Sends image/video/audio/document. media_type: image | video | audio | document |
wait | delay_value, delay_unit | Pauses the sequence. delay_unit: minutes | hours | days |
branch | condition, field, operator, value, yes_step, no_step | Branches based on contact data or tag |
{
"user_id": "702cc6ac-...",
"name": "New Student Onboarding",
"phone_number_id": "f3a1bc90-...",
"timezone": "Africa/Lagos",
"delivery_window_start": "09:00",
"delivery_window_end": "20:00",
"steps": [
{
"step_order": 1,
"step_type": "send_text",
"config": {
"body": "Hello {{first_name}}! Welcome to Funcare School. We are excited to have you."
}
},
{
"step_order": 2,
"step_type": "wait",
"config": { "delay_value": 1, "delay_unit": "days" }
},
{
"step_order": 3,
"step_type": "send_text",
"config": {
"body": "Hi {{first_name}}, just checking in. Have you completed your registration form?"
}
},
{
"step_order": 4,
"step_type": "wait",
"config": { "delay_value": 2, "delay_unit": "days" }
},
{
"step_order": 5,
"step_type": "send_image",
"config": {
"url": "https://yourcdn.com/school-brochure.jpg",
"caption": "Here is our school brochure, {{first_name}}. See you soon!"
}
}
]
}
{
"id": "seq-uuid-here",
"name": "New Student Onboarding",
"status": "active"
}Enrollments
Enroll contacts into a sequence — Wamator walks them through each step automatically, respecting delays, timezone delivery windows, rate limits, and opt-outs.
{
"contact_ids": [
"ccc33333-0000-0000-0000-000000000001",
"ddd44444-0000-0000-0000-000000000002"
],
"phone_number_id": "f3a1bc90-..."
}
{
"enrolled": 2,
"skipped": 0,
"enrollment_ids": [
"enr-uuid-1",
"enr-uuid-2"
],
"skipped_details": []
}{
"page": 1,
"limit": 50,
"enrollments": [
{
"id": "enr-uuid-1",
"contact_id": "ccc33333-...",
"contact_name": "Mrs. Adaeze Obi",
"phone_number": "2348012345678",
"current_step_order": 2,
"status": "active",
"enrolled_at": "2026-07-03T10:00:00Z",
"next_run_at": "2026-07-04T09:00:00Z"
}
]
}Broadcasts
One-shot blasts to a list, a tag group, or selected contacts. Messages go into the priority queue and are sent respecting rate limits and opt-outs. Template variables are substituted per contact.
{{first_name}}, {{name}}, {{phone}}, {{email}} in the message body — replaced per contact before sending.// Blast to everyone in a list
{
"user_id": "702cc6ac-...",
"phone_number_id": "f3a1bc90-...",
"message_type": "text",
"body": "Hello {{first_name}}, term fees are due this Friday. Please ensure payment is made. Thank you — Funcare School.",
"list_id": "a1b2c3d4-..."
}
// Blast to all contacts with a specific tag
{
"user_id": "702cc6ac-...",
"phone_number_id": "f3a1bc90-...",
"message_type": "text",
"body": "Exclusive offer for {{first_name}} — your VIP discount expires tonight!",
"tag_id": "e5f6a7b8-..."
}
// Send an image to a list
{
"user_id": "702cc6ac-...",
"phone_number_id": "f3a1bc90-...",
"message_type": "image",
"media_url": "https://yourcdn.com/flyer.jpg",
"caption": "Our end of term event is here! See you {{first_name}} \ud83c\udf89",
"list_id": "a1b2c3d4-..."
}
{
"batch_id": "bcast-uuid-here",
"queued": 240,
"total_contacts": 242,
"skipped_opted_out": 2
}GET /broadcasts/{batch_id}{
"batch_id": "bcast-uuid-here",
"messages": [
{
"to_number": "2348012345678",
"status": "sent",
"processed_at": "2026-07-03T10:05:00Z"
},
{
"to_number": "2348099999999",
"status": "pending",
"processed_at": null
}
]
}Opt-outs
When a contact replies STOP to any message, Wamator automatically opts them out — cancels all active enrollments, removes them from future sends, and updates their contact record. You can also manage opt-outs manually.
{
"total_opted_out": 3,
"opt_outs": [
{
"phone_number": "2348012345678",
"contact_name": "Mrs. Adaeze Obi",
"opted_out_at": "2026-07-02T14:30:00Z",
"is_active": true
}
]
}{
"user_id": "702cc6ac-...",
"phone_number": "2348012345678"
}
{ "message": "Contact opted out", "phone_number": "2348012345678" }Webhooks
Register a URL on your server and Wamator will POST real-time events to it — message delivery, opt-outs, completed sequences, unhealthy numbers. No polling required.
{
"url": "https://edumess.com/api/wamator-events",
"secret": "your-signing-secret-here",
"events": [
"message.sent",
"message.delivered",
"message.failed",
"contact.opted_out",
"enrollment.completed",
"number.unhealthy"
]
}
{
"status": "ok",
"url": "https://edumess.com/api/wamator-events",
"events": ["message.sent", "message.delivered", ...]
}events to receive all of them.Wamator will POST this JSON structure to your URL for every event:
// message.sent
{
"event": "message.sent",
"timestamp": "2026-07-03T10:05:00Z",
"data": {
"to": "2348012345678",
"batch_id": "bcast-uuid-here",
"enrollment_id": null,
"message_type": "text"
}
}
// message.delivered
{
"event": "message.delivered",
"timestamp": "2026-07-03T10:05:04Z",
"data": {
"to": "2348012345678",
"batch_id": "bcast-uuid-here"
}
}
// message.failed
{
"event": "message.failed",
"timestamp": "2026-07-03T10:07:00Z",
"data": {
"to": "2348099999999",
"batch_id": "bcast-uuid-here",
"error": "Max retries exceeded"
}
}
// contact.opted_out
{
"event": "contact.opted_out",
"timestamp": "2026-07-03T11:00:00Z",
"data": {
"phone_number": "2348055555555",
"contact_name": "Mrs. Adaeze Obi"
}
}
// enrollment.completed
{
"event": "enrollment.completed",
"timestamp": "2026-07-05T09:00:00Z",
"data": {
"enrollment_id": "enr-uuid-here",
"contact_id": "ccc33333-...",
"sequence_id": "seq-uuid-here"
}
}
// number.unhealthy
{
"event": "number.unhealthy",
"timestamp": "2026-07-03T12:00:00Z",
"data": {
"phone_number_id": "f3a1bc90-...",
"phone_number": "2349063570004",
"reason": "10+ failed sends in the last hour — number auto-paused"
}
}
Every request includes an X-Wamator-Signature header. Verify it to ensure the request is from Wamator and not a third party.
// Node.js
const crypto = require('crypto');
function verifySignature(body, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body) // raw request body as Buffer
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// Express example
app.post('/api/wamator-events', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['x-wamator-signature'];
if (!verifySignature(req.body, sig, process.env.WAMATOR_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
// handle event.event ...
res.json({ received: true });
});
// PHP
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WAMATOR_SIGNATURE'];
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('WAMATOR_SECRET'));
if (!hash_equals($expected, $sig)) {
http_response_code(401); exit;
}
$event = json_decode($body, true);
{
"page": 1,
"logs": [
{
"event": "message.delivered",
"delivered": true,
"response_status": 200,
"response_body": "{"received":true}",
"attempt": 1,
"created_at": "2026-07-03T10:05:04Z"
},
{
"event": "contact.opted_out",
"delivered": false,
"response_status": 500,
"response_body": "Internal Server Error",
"attempt": 1,
"created_at": "2026-07-03T11:00:00Z"
}
]
}POST /webhooks/openwa is internal — the WhatsApp gateway calls it automatically. Never call it from your application.Vendor Plans
White-label plan configurations. You define your own plan names and selling prices on top of Wamator's base plans. Your end-users see only your branding and prices — never Wamator's.
{
"plan_id": "a741616a-9725-4737-b67c-59811277685e",
"custom_name": "Edumess Starter",
"custom_description": "For small schools — up to 1000 contacts, 1000 messages/month",
"selling_price": 8000,
"currency": "NGN"
}
{
"status": "ok",
"plan_config": {
"id": "vpc-uuid-here",
"plan_id": "a741616a-...",
"custom_name": "Edumess Starter",
"selling_price": 8000.00,
"currency": "NGN",
"is_active": true
}
}vendor_plan_config_id when initiating a payment.Accept Payment
Two ways to collect subscription payments from your end-users. Both use Paystack under the hood. Choose based on how much UI you want to build yourself.
| Approach | When to use | You build |
|---|---|---|
| Hosted Checkout Recommended | You want the fastest path. Redirect your user to a Wamator-hosted page — Wamator handles all the UI, billing period selection, and Paystack redirect. | Nothing. Just a redirect URL. |
| API Flow (POST /pay/init) | You want full control of your own UI — custom checkout page, embedded modal, etc. | Your own checkout UI. You call the API and redirect to the Paystack link. |
id (user_id) in your own DB.https://app.wamator.com/checkout/{vendor_id}?user_id={user_id}API: call
POST /pay/init from your backend → get payment_link → redirect user to itreturn_url. Show a success screen. No polling needed — subscription is already active.Call this from your backend server (never from frontend — your API key must stay secret). Pass the sub-user ID, the plan config they chose, and where to send them after payment.
{
"user_id": "702cc6ac-8832-447c-8b7a-270ed1841a6b",
"vendor_plan_config_id": "vpc-uuid-here",
"customer_email": "admin@funcare.com",
"customer_name": "Funcare School",
"customer_phone": "2348012345678",
"return_url": "https://edumess.com/payment/complete"
}
{
"status": "ok",
"session_id": "ps-uuid-here",
"payment_link": "https://checkout.paystack.com/abcdef123456",
"reference": "wam-a1b2c3d4e5f6g7h8i9j0"
}return_url. The subscription is already active by then — no polling needed.| Field | Type | Notes |
|---|---|---|
user_id | UUID | Required — must already exist via POST /users/register |
vendor_plan_config_id | UUID | Required — get from GET /vendor-plans/ |
customer_email | string | Required — shown on Paystack page |
customer_name | string | Required — shown on Paystack page |
customer_phone | string | Optional |
return_url | string | Where Paystack sends the user after payment. Use your own app URL. |
Complete server-side flow in Node.js (Express):
// 1. School signs up in Edumess — register them in Wamator
const userRes = await fetch('https://api.wamator.com/users/register', {
method: 'POST',
headers: { 'x-api-key': process.env.WAMATOR_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
name: school.name,
email: school.email,
external_id: school.id // your DB id — link back later
})
});
const { id: wamatorUserId } = await userRes.json();
// Save wamatorUserId in your DB against this school
// 2. School clicks "Subscribe to Messaging" in Edumess
// Call from your backend:
const payRes = await fetch('https://api.wamator.com/pay/init', {
method: 'POST',
headers: { 'x-api-key': process.env.WAMATOR_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: wamatorUserId,
vendor_plan_config_id: 'vpc-uuid-here', // your plan config
customer_email: school.email,
customer_name: school.name,
return_url: 'https://edumess.com/payment/complete'
})
});
const { payment_link } = await payRes.json();
// 3. Redirect the school to Paystack
res.redirect(payment_link);
// 4. Paystack calls Wamator webhook automatically
// Subscription activates. Your wallet is credited. Done.
Hosted Checkout Page
Skip building a payment UI entirely. Wamator hosts a branded checkout page for your vendor account. Register your user, then redirect them to the URL below — they pick a plan, pay, and are sent back to your app.
user_id. Save it in your database against your user record.&plan={plan_config_id} to pre-select a specific plan// Minimum — user picks their own plan https://app.wamator.com/checkout/7a689d72-...?user_id=702cc6ac-... // Pre-select a plan (user just confirms and pays) https://app.wamator.com/checkout/7a689d72-...?user_id=702cc6ac-...&plan=vpc-uuid-here
| Element | Source |
|---|---|
| Brand name / logo | Your white_label_brand_name and white_label_logo — set in dashboard Settings. Falls back to Wamator branding. |
| User's name | Fetched from Wamator using user_id — user sees their own name. No typing needed. |
| Plan list | All active vendor plan configs you created via POST /vendor-plans/ |
| Current plan | If the user already has an active subscription, it's highlighted — they can upgrade to another plan |
| Billing period | User selects Monthly / Quarterly (−10%) / Annual (−20%) |
| After payment | Redirects to https://app.wamator.com/checkout/{vendor_id}?status=success by default |
{
"vendor_id": "7a689d72-...",
"brand_name": "Edumess",
"logo_url": "https://cdn.example.com/edumess-logo.png",
"brand_color": "#1DBD8C",
"plans": [
{
"id": "vpc-uuid-here",
"name": "School Basic",
"description": "For schools up to 500 students",
"price": 8000.00,
"currency": "NGN",
"max_messages": 5000,
"max_contacts": 500
}
]
}{
"id": "702cc6ac-...",
"name": "Funcare School",
"email": "admin@funcare.com",
"active_plan_config_id": "vpc-uuid-here",
"active_plan_name": "School Basic",
"active_ends_at": "2026-08-05T00:00:00Z",
"active_billing_period": "monthly"
}active_plan_config_id is null if the user has no current subscription. The checkout page uses this to highlight their current plan and disable re-subscribing to the same plan.user_id in the checkout URL is a Wamator UUID — not sensitive on its own, but don't expose your vendor_id + user_id combination in public links if your users shouldn't be able to see each other's plan status. For internal dashboards and app redirects, this is perfectly safe.Wallet & Payouts
Every time a subscription payment succeeds, Wamator credits your earnings to your wallet. You can view your balance and withdraw to your bank account at any time.
{
"status": "ok",
"balance": 24500.00,
"currency": "NGN",
"transactions": [
{
"type": "credit",
"amount": 6500.00,
"balance_after": 24500.00,
"description": "Subscription payment received",
"reference": "wam-a1b2c3d4e5f6",
"created_at": "2026-07-05T10:00:00Z"
},
{
"type": "debit",
"amount": 10000.00,
"balance_after": 18000.00,
"description": "Withdrawal request",
"reference": "wd-x1y2z3w4v5",
"created_at": "2026-07-03T14:00:00Z"
}
]
}{
"bank_account_no": "0123456789",
"bank_code": "058",
"bank_account_name": "Edumess Technology Ltd"
}
{ "status": "ok", "message": "Bank details saved" }GET /wallet/banks — returns all Nigerian banks with their Paystack codes.{
"status": "ok",
"banks": [
{ "name": "Access Bank", "code": "044" },
{ "name": "GTBank", "code": "058" },
{ "name": "Zenith Bank", "code": "057" },
{ "name": "First Bank of Nigeria", "code": "011" }
]
}{
"amount": 10000
}
{
"status": "ok",
"withdrawal_id": "wd-uuid-here",
"amount": 10000,
"reference": "wd-x1y2z3w4v5u6",
"message": "Withdrawal initiated — funds will arrive in your bank account shortly"
}POST /wallet/bank-details before withdrawing. Minimum withdrawal is ₦100.Payment Webhook
Wamator receives payment confirmations from Paystack automatically. You do not need to call or configure this endpoint — it is documented here for transparency and debugging.
https://api.wamator.com/webhooks/paystack in your Paystack dashboard under Settings → API Keys & Webhooks.The endpoint Paystack calls (internal — do not call this yourself):
POST https://api.wamator.com/webhooks/paystack
// Headers sent by Paystack
x-paystack-signature: sha512 HMAC of raw body
// Payload (charge.success event)
{
"event": "charge.success",
"data": {
"id": 12345678,
"reference": "wam-a1b2c3d4e5f6g7h8i9j0",
"amount": 800000,
"currency": "NGN",
"status": "success"
}
}