WhatsApp selling
WhatsApp selling means your customers order from you on WhatsApp: they ask for your price list, choose what they want, confirm, and pay. Your products and orders can stay on your own website or shop system, so you build nothing new for payments or order tracking. This guide covers the two ways to use it: ordering inside the WhatsApp chat, and orders created by your website or app through the API. Both give you the same order events and the same delivery codes.
POST /v1/orders). We create the order and the payment, and give you a checkout_url.order.paid webhook. Your site starts packing and delivering.POST /v1/orders/{id}/fulfil). We can tell the buyer on WhatsApp at each step.order.* events.curl -X POST https://api.identifyorg.com/v1/orders \
-H "X-IdentifyOrg-Key: io_test_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"reference": "WEB-1001",
"customer": {
"name": "Tunde Bello",
"email": "tunde@example.com",
"phone": "08031234567",
"address": "12 Admiralty Way, Lekki"
},
"items": [
{ "name": "Red shoes", "unit_price": 15000, "quantity": 2 },
{ "name": "Gift wrap", "unit_price": 500 }
],
"delivery_fee": 1500,
"redirect_url": "https://yourshop.com/orders/WEB-1001/thanks"
}'{
"id": "so_4c1d9a...",
"reference": "WEB-1001",
"status": "awaiting_payment",
"total": 32000.0,
"currency": "NGN",
"checkout_url": "https://identifyorg.com/pay/mp_3f9a...",
"payment_reference": "mp_3f9a...",
"duplicate": false
}Send your own reference (your order number). If your site retries, or the customer double clicks, sending the same reference again returns the same order and never creates a second payment. Each item is either a line you describe yourself (name and unit_price), or a product_id from the catalog you keep with us, so prices always come from one place.
// On your server, when the customer taps "Place order"
const res = await fetch("https://api.identifyorg.com/v1/orders", {
method: "POST",
headers: { "X-IdentifyOrg-Key": process.env.IDENTIFYORG_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
reference: order.number, // your own order number
customer: { name: user.name, email: user.email, phone: user.phone, address: order.address },
items: order.lines.map((l) => ({ name: l.title, unit_price: l.price, quantity: l.qty })),
delivery_fee: order.shipping,
redirect_url: `https://yourshop.com/orders/${order.number}/thanks`,
}),
});
const o = await res.json();
// Send the customer to pay:
redirect(o.checkout_url);Use a webhook. Events: order.created, order.paid, order.fulfilled, order.cancelled. Each carries the whole order and your metadata. The same event can arrive more than once, so store the event id and ignore a repeat. See the webhooks guide for signatures and retries.
// Your webhook endpoint (set it up under Webhooks in your dashboard)
app.post("/identifyorg/webhook", express.raw({ type: "*/*" }), (req, res) => {
// 1. Check the signature (see the Webhooks guide)
const event = JSON.parse(req.body);
if (alreadyHandled(event.id)) return res.sendStatus(200); // the same event can arrive twice
if (event.event === "order.paid") markOrderPaid(event.data.reference); // release stock, start packing
if (event.event === "order.cancelled") releaseStock(event.data.reference);
res.sendStatus(200);
});Never trust the redirect back to your site as proof of payment. Wait for order.paid, or read the order with GET /v1/orders/{id}.
curl -X POST https://api.identifyorg.com/v1/orders/so_4c1d9a.../fulfil \
-H "X-IdentifyOrg-Key: io_live_sk_xxxxxxxx"An unpaid order can be cancelled with POST /v1/orders/{id}/cancel. A paid order is refunded with POST /v1/business/refunds.
Link a WhatsApp number to a workspace (see the WhatsApp guide), ask us to switch on service notices for it, then add the customer's phone and your whatsapp_channel_id to the order. The buyer gets the payment link, a receipt when they pay, and a message when you mark the order sent. Every message ends with a way to reply STOP. If a number is not on WhatsApp, the response says so and you can send an SMS instead.
No website needed. Your customers order in the chat: they ask for the price list, pick items, confirm, send an address and pay by link. You set it up once, and every order arrives as a normal order in the API, with the same webhooks.
# Switch on ordering in the chat (once per number)
curl -X POST https://api.identifyorg.com/v1/wa/channels/$ID/ordering \
-H "X-IdentifyOrg-Key: io_live_sk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{ "enabled": true, "delivery_fee": 1500, "ask_address": true }'
# Your products and prices (the price list is built from these)
curl -X POST https://api.identifyorg.com/v1/catalog/products \
-H "X-IdentifyOrg-Key: io_live_sk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{ "name": "Red shoes", "price": 15000, "sku": "SH-1" }'Customer: menu
Your number: *Ada Stores price list*
1. Red shoes - ₦15,000
2. Blue cap - ₦3,000
To order, reply with the item number and how many, like: 1x2, 3
Customer: 1x2, 2
Your number: Your order: 2 x Red shoes - ₦30,000, 1 x Blue cap - ₦3,000, Delivery - ₦1,500
Total: ₦34,500. Reply YES to confirm.
Customer: yes
Your number: Great. Please send your delivery address.
Customer: 12 Admiralty Way, Lekki, Lagos
Your number: Thank you! Order wa-31c2: Pay securely here: https://identifyorg.com/pay/mp_3f9a...
(customer pays) -> you get an order.paid webhook
Your number: We received your payment of NGN 34,500. Your delivery code is 4821.POST /v1/orders/{id}/confirm-delivery. A right code closes the order as delivered. This stops orders being collected by the wrong person.Keep it. Your products, stock and orders stay on your own system, and our WhatsApp number just becomes another way for your customers to order. There are two parts, and you choose each one.
PUT /v1/catalog/sync whenever they change. The list is read fresh, so a price change on your site shows in the chat within a minute.checkout: "own" and every confirmed order is sent to your website, with the items, the customer's WhatsApp number and delivery address. Whatever your site answers (a payment link, or "we will call you") is sent to the customer. Or choose identifyorg and we also take the payment and tell you with order.paid.# Your products stay on your website: give us the address of your product list (a JSON array)
# https://yourshop.com/products.json -> [ { "id": "w-1", "name": "Ankara dress", "price": 18000, "sku": "AD-1" }, ... ]
# and choose "own": every confirmed order is sent to YOUR system (the website address you connected to this number)
curl -X POST https://api.identifyorg.com/v1/wa/channels/$ID/ordering \
-H "X-IdentifyOrg-Key: io_live_sk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{ "enabled": true, "catalog_url": "https://yourshop.com/products.json", "checkout": "own", "delivery_fee": 1500 }'
# Prefer to push your products instead of us reading a list? Send them whenever they change:
curl -X PUT https://api.identifyorg.com/v1/catalog/sync \
-H "X-IdentifyOrg-Key: io_live_sk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{ "products": [ { "id": "w-1", "name": "Ankara dress", "price": 18000, "sku": "AD-1" } ], "remove_missing": true }'# What your system receives (signed, like every message we send you) when the customer confirms:
POST https://yourshop.com/whatsapp
X-IdentifyOrg-Event: order
{
"type": "order",
"id": "wa-31c2e8a1-1762000000",
"order": {
"reference": "wa-31c2e8a1-1762000000",
"items": [ { "name": "Ankara dress", "sku": "AD-1", "quantity": 2, "unit_price": 18000, "line_total": 36000 } ],
"delivery_fee": 1500, "total": 37500, "currency": "NGN",
"customer": { "name": "Tunde", "phone": "2348031234567", "address": "5 Allen Avenue, Ikeja, Lagos" },
"source": "whatsapp"
}
}
# You answer with the message the customer should see, for example your own payment link:
{ "reply": "Thanks Tunde! Pay for your order here: https://yourshop.com/pay/9981" }Connect your website address to the number first (see the WhatsApp guide). Your system should answer within a few seconds; if it does not answer, the customer is told a person will reply and the chat is flagged for you.
Prove that a user owns the phone number they gave you before you create their account, let them log in, or accept an order from your website. The code goes to their WhatsApp. It is stored only as a one-way hash, expires in minutes, and is limited to a few tries.
# 1. Your user asks for a code (sign up, log in, confirm an order)
curl -X POST https://api.identifyorg.com/v1/wa/channels/$ID/otp \
-H "X-IdentifyOrg-Key: io_live_sk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{ "to": "2348031234567", "brand": "Ada Stores", "length": 6, "ttl_minutes": 10, "reference": "signup-5521" }'
# 2. Your user types the code on your site; you check it
curl -X POST https://api.identifyorg.com/v1/wa/channels/$ID/otp/verify \
-H "X-IdentifyOrg-Key: io_live_sk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{ "to": "2348031234567", "code": "482915" }'
# -> { "valid": true, "reason": null, "reference": "signup-5521" }POST /v1/orders and the same phone. The buyer then also gets the payment link and receipt on WhatsApp.POST /v1/wa/channels/{id}/verify-identity (for example a date of birth from your records).For notices a person expects about their own vehicle, order, booking, delivery or account (never marketing), we can switch on service notices for your number after a quick look at your business. Use POST /v1/wa/channels/{id}/notify with the number, the text, your own reference and opted_in: true to confirm your customer agreed. Every message ends with a way to reply STOP. A person who stops is never messaged again, the same reference is never sent twice, and a number that is not on WhatsApp is reported so you can send an SMS instead. See the endpoint.
# With a test key nothing is charged. Pretend the customer paid:
curl -X POST https://api.identifyorg.com/v1/business/test/simulate-card-payment \
-H "X-IdentifyOrg-Key: io_test_sk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{ "reference": "mp_3f9a..." }'Test orders and live orders are separate: a test key only sees test orders. Open the checkout_url of a test order to see the page your customers will see.
order.paid, never from the redirect.reference on every order, so retries are safe.| Status | Code | Meaning |
|---|---|---|
| 400 | business_profile_required | Add your business name and type under Take Payments first. |
| 400 | item_incomplete | Each item needs a name and a unit_price, or a product_id. |
| 400 | empty_order | The total must be more than zero (the smallest payment is ₦100). |
| 400 | unknown_product | The product_id is not in your catalog. |
| 404 | order_not_found | No such order for this key (test and live orders are separate). |
| 409 | not_paid | Only a paid order can be marked as sent. |
| 400 | wrong_code | The delivery code does not match this order (5 wrong tries lock it). |
| 409 | already_paid | A paid order cannot be cancelled: refund the payment instead. |
Every endpoint is in the API reference.
Finding this hard to integrate?
Open a ticket and our technical team will help you get it working. We reply by email. Or WhatsApp or call +234 913 995 7618.