For developers
Receiving WhatsApp orders on a pharmacy website
A pharmacy using Miadi can have its WhatsApp assistant send customer orders to its own website. Your website creates an unpaid order and answers with a checkout link; the assistant gives that link to the customer, who pays on your website. The pharmacy confirms the order before dispensing.
1. What you build
- One https address on your site that accepts
POSTrequests with a JSON body, for examplehttps://yourpharmacy.example/wp-json/miadi/v1/order. - The pharmacy enters it in Miadi under Settings → Your online shop → Send the order to my website, then gives you the signing secret shown there.
- Answer within 8 seconds. Redirects are not followed, so give the final address.
2. What you receive
Headers: Content-Type: application/json, Miadi-Event (order.requested or order.test) and Miadi-Signature. The body (this example is from a pharmacy that prices in US dollars):
{
"event": "order.requested",
"ref": "K7P4QX",
"created_at": "2026-10-21T18:42:10.000Z",
"source": "whatsapp",
"pharmacy": { "name": "Riverside Pharmacy", "miadi_id": "riverside-pharmacy" },
"customer": { "phone": "12025550123", "name": "Sam" },
"items": [
{ "name": "Paracetamol 500mg x16", "sku": "PARA16", "quantity": 2, "unit_price_kes": 4.5, "url": "https://yourpharmacy.example/product/paracetamol" },
{ "name": "ORS sachets", "sku": null, "quantity": 4, "unit_price_kes": 1.2, "url": null }
],
"delivery": { "method": "delivery", "area": "Old town", "zone": "North side, Old town", "fee_kes": 3, "details": "Flat 2, next to the bakery" },
"currency": "USD",
"subtotal_kes": 13.8,
"total_kes": 16.8,
"note": "Sent by the pharmacy's WhatsApp assistant at the customer's request. Not paid and not confirmed: your pharmacist reviews it before dispensing."
}- Items come only from the pharmacy's Miadi price list, at its listed price. Match them to your products by
skuwhere set, otherwise byname. Always use your own current prices and stock. - Prescription-only items are never sent: the assistant passes those to the pharmacist.
- Every amount is in
currency, the pharmacy's own (KES in Kenya, UGX in Uganda, NGN in Nigeria…), as a number with up to two decimals (12.5 means 12.50). The_kesfield names stay the same in every country. delivery.fee_kesandtotal_kesarenullwhen the customer's area is not on the pharmacy's delivery list. Your site (or the pharmacist) sets the fee.refis the reference the customer sees in WhatsApp. Store it with the order.
3. Check it really came from Miadi
Miadi-Signature looks like t=1792607000,v1=5f2b…. Compute HMAC-SHA256 of t + "." + raw body with the signing secret, compare it with v1 in constant time, and refuse anything older than 5 minutes. Refuse the request if the check fails.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string), before any JSON parsing.
function isFromMiadi(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes: refuse
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return parts.v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}<?php
// WordPress / WooCommerce: register a REST route, then check the signature first.
function miadi_is_genuine( $raw_body, $header, $secret ) {
parse_str( str_replace( ',', '&', $header ), $parts );
$t = intval( $parts['t'] ?? 0 );
if ( ! $t || abs( time() - $t ) > 300 ) return false;
$expected = hash_hmac( 'sha256', $t . '.' . $raw_body, $secret );
return hash_equals( $expected, $parts['v1'] ?? '' );
}
add_action( 'rest_api_init', function () {
register_rest_route( 'miadi/v1', '/order', [
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function ( WP_REST_Request $req ) {
$raw = $req->get_body();
if ( ! miadi_is_genuine( $raw, $req->get_header( 'miadi_signature' ), MIADI_SECRET ) ) {
return new WP_REST_Response( [ 'error' => 'bad signature' ], 401 );
}
$o = json_decode( $raw, true );
if ( $o['event'] === 'order.test' ) return [ 'message' => 'Test received' ];
// Create a pending order (wc_create_order), add items by SKU, set the phone, then:
// return [ 'checkout_url' => $order->get_checkout_payment_url(), 'order_id' => (string) $order->get_id() ];
},
] );
} );4. What to answer
Any 2xx status means the order was received. Optionally return JSON; every field is optional (the same pharmacy):
HTTP/1.1 200 OK
Content-Type: application/json
{
"checkout_url": "https://yourpharmacy.example/checkout/order-pay/1234/?key=wc_order_abc",
"order_id": "1234",
"message": "Pay by card on our website. We deliver within 2 hours of payment."
}checkout_urlmust start with https. The assistant sends it to the customer under its reply. Without it, the customer is told the pharmacy will contact them.message(up to 300 characters) is passed to the assistant, which may repeat it to the customer.- Any other status, or no answer within 8 seconds, counts as failed. Nothing is retried: the order goes to the pharmacist in the Miadi inbox instead, so the customer is never left waiting.
- For
order.test, just answer 200. Test orders carry no customer details.
Good practice
- Treat each order as unpaid and unconfirmed until payment clears and the pharmacist approves it.
- The customer's phone number and order are personal health information under data protection law (in Kenya, the Data Protection Act, 2019). Keep them only as long as you need them, and never log the signing secret.
- If the secret may have leaked, the pharmacy can make a new one in Miadi; update your site at the same time.