Wapzio logo

Connect a custom website or CRM

Any platform can send carts and orders: post JSON to your store's ingest URL and sign it with the store's secret.

6 min read Updated 19 Sept 2026Custom storeDeveloper
On this page (5)

If your shop is not Shopify or WooCommerce — a custom site, a Laravel or CodeIgniter build, a headless storefront, an in-house CRM — it connects with two HTTP requests: one when a cart changes, one when an order changes. This guide is for whoever maintains that code.

Hand this page to your developer

Everything below is copy-and-paste ready. A working integration is usually an afternoon's work, and most of it is deciding where in your checkout the cart event belongs.

Step 1 — Create the store and copy its ingest URL

  1. 1

    Add the store

    Commerce → Stores → Add store → Custom store. Enter the store name, the Currency it sells in (INR), and the Default country code for its shoppers (91).

  2. 2

    Why those two fields matter

    Shopify and WooCommerce report their own currency; a custom store has to be told. And an Indian checkout usually posts 7709638776 with no country code — WhatsApp cannot deliver to that, so Wapzio prefixes the default. Numbers that already look international are left alone.

  3. 3

    Copy the ingest URL

    Open Connection details on the new card and copy the Ingest URL. It looks like this:

    Ingest URL
    https://api.wapzio.com/api/ecommerce/webhook/custom/<your-store-token>
    Watch out

    Treat it like a password: the token in the path identifies your store. Keep it on your server, never in browser JavaScript or a mobile app. If it leaks, use Rotate token on the card's ⋯ menu.

Step 2 — Post the cart

Send a cart whenever it changes in a way worth reminding about: an item added, the checkout form filled in, payment started and not finished. Posting the same cart_id again updates that one cart instead of creating another.

POST to the ingest URL — cart
{
  "event": "cart_updated",
  "cart_id": "CART-10432",
  "phone": "919876543210",
  "email": "asha@example.com",
  "first_name": "Asha",
  "currency": "INR",
  "total": 1559,
  "checkout_url": "https://myshop.com/cart/recover/CART-10432",
  "whatsapp_opt_in": true,
  "consent_text": "Send me order updates on WhatsApp",
  "items": [
    {
      "product_id": "SKU-91",
      "title": "Cast Iron Kadai 10 inch",
      "quantity": 1,
      "price": 1499,
      "url": "https://myshop.com/product/cast-iron-kadai-10",
      "image_url": "https://myshop.com/img/kadai.webp"
    }
  ]
}
FieldRequiredNotes
cart_idYesYour own id for this cart. The same value later updates the same cart. id, token and cart_key are accepted too.
phoneYes, in practiceThe shopper's number. Without it the cart is stored but can never be messaged. mobile, customer.phone and customer.mobile are accepted.
totalYesCart value as a number, no currency symbol. subtotal is accepted.
currencyRecommendedFalls back to the store's currency.
checkout_urlRecommendedThe link the reminder button opens. Make it restore the cart, or point at a pay page for that order. cart_url and recovery_url are accepted.
first_name / emailRecommendedUsed in the message copy and to match the shopper to a contact.
whatsapp_opt_inRecommendedtrue when the shopper ticked a consent box. Pair it with consent_text, the exact wording they saw.
items[]Recommendedtitle, quantity, price, url, image_url. qty, name and unit_price are accepted.
Never put order_id or order_number in a cart payload

Wapzio reads the event type from the body, and anything carrying an order id or order number is treated as an order, not a cart. A cart posted with those fields silently becomes an order event.

Step 3 — Post the order

Post an order event when payment succeeds and again at every stage change. The order_placed event is what stops the cart reminders, so send it as soon as payment is confirmed — including from your payment gateway's webhook, not only from the browser redirect.

POST to the same ingest URL — order
{
  "event": "order_placed",
  "order_id": "10871",
  "order_number": "KK-20260917-A8B838",
  "cart_id": "CART-10432",
  "phone": "919876543210",
  "first_name": "Asha",
  "currency": "INR",
  "total": 1559,
  "payment_method": "prepaid",
  "financial_status": "paid",
  "items": [
    { "title": "Cast Iron Kadai 10 inch", "quantity": 1, "price": 1499 }
  ]
}
eventWhen to send itWhat Wapzio does
order_placedPayment confirmed, or a Cash on Delivery order accepted by your shop.Closes the matching cart as Recovered, stops its reminders, and sends the Order placed or COD confirmation message.
order_shippedYou hand the parcel to the courier. order_fulfilled works too.Sends the shipping update. Include tracking_number, tracking_url and tracking_company for the copy.
order_deliveredThe courier marks it delivered.Sends the delivered message, and starts the feedback timer if that message is on.
order_cancelledYour shop cancels the order.Sends the cancellation message. Never revives cart reminders.
  • Send cart_id on the order when you have it. Wapzio also matches on the phone number, but the cart id is exact.
  • payment_method: "cod" (or cod: true) is what triggers the Cash on Delivery confirmation with its Confirm and Cancel buttons.
  • Posting order_placed twice for one order is safe — the second one is recognised and not messaged again.

Step 4 — Sign every request

Two headers turn your ingest URL from a secret address into a verified sender. Reveal the store's signing secret under Connection details → Request signing → Show signing secret.

HeaderValue
X-Wapzio-TimestampCurrent time in unix seconds. More than five minutes out and the request is refused.
X-Wapzio-Signaturesha256= followed by HMAC-SHA256 of "<timestamp>.<exact request body>", keyed with the signing secret.
Node.js
const body = JSON.stringify(cart);
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = "sha256=" + crypto
  .createHmac("sha256", SIGNING_SECRET)
  .update(`${timestamp}.${body}`)
  .digest("hex");

await fetch(INGEST_URL, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Wapzio-Timestamp": timestamp,
    "X-Wapzio-Signature": signature,
  },
  body, // the exact string that was signed
});
PHP
$body      = json_encode($cart);
$timestamp = (string) time();
$signature = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, SIGNING_SECRET);

// POST $body with headers:
//   Content-Type: application/json
//   X-Wapzio-Timestamp: $timestamp
//   X-Wapzio-Signature: $signature
Sign the bytes you send

Build the JSON string once, sign that string, and send that same string. Re-encoding the object for the request — a different key order, different spacing — produces a signature that cannot verify.

  1. 1

    Check what Wapzio saw

    Every response tells you where you stand, so log it while you are building:

    Response
    { "success": true, "signature": "verified" }
    signatureMeaning
    verifiedCorrect. The store card shows Signed requests verified.
    not_signedThe two headers were missing. Accepted for now, refused once you require signing.
    invalidThe headers were there but did not match; signature_error says why.
  2. 2

    Then make it mandatory

    Once the store card reads Signed requests verified, switch Only accept signed requests on. Unsigned posts are refused from then on.

Step 5 — Make delivery reliable

A cart event that never arrives is a reminder that never sends, and a shopper waiting on a checkout page should never wait on Wapzio. These four habits are what separate an integration that works on the demo from one that works on a bad Tuesday.

  • Never block checkout on the call. Write the event to a table of your own, answer the shopper, and send it after the response — or from a queue worker.
  • Retry failures with a backoff (a minute, two, four…) and keep each order's events in order, so a retried order_placed cannot land after the shipping update that followed it.
  • Before sending a queued cart event, check the order is still unpaid. A cart event delivered late, after payment, starts reminders for someone who already bought.
  • Set a short timeout (five seconds is plenty) and send a User-Agent your logs can recognise, for example MyShop-Wapzio/1.0. Requests with no agent at all can be refused by the API's bot protection.
What to watch while testing

Commerce → Abandoned Carts shows the cart, the shopper, and every reminder sent, skipped or failed for it. Commerce → Stores shows the last event received and whether it was signed. Between them you can tell a payload problem from a template problem without reading a single log file.