Njiwa for PrestaShop
WhatsApp your customers on your shop's own order statuses, the ones your payment and shipping modules added among them, and get a message yourself when an order comes in.
What changes for your business
Your customer hears it from you before they ask
Most of what a small shop answers all day is one question, asked by people who have no way of knowing the answer. Every status an order passes through, from waiting on a bank transfer through paid to shipped, is one your customer now hears about as it happens, on the app already open on their phone. PrestaShop's own email goes to an address that can be mistyped at a checkout or filed under promotions; this goes to the number they gave you to be reached on, and to the mobile one where the address carries both, because a landline is a message nobody ever sees. A local number, 0712 345 678, is completed from the country on that address, and one already written in full, +254 712 345 678, keeps the country code the customer wrote, so it reaches the same phone either way.
It works with the statuses your shop actually uses
Status numbers are different in every shop, and yours has probably been renamed, migrated, or added to by a payment gateway or a shipping tool. None of that is assumed here. The settings page reads your own shop and prints, in your own language, which of your statuses each message is attached to, and says outright when one does not exist here at all. You learn that a message could never fire while you are still setting this up, rather than from a customer who was never told.
A cancellation or a refund does not become a phone call
The two moments a customer is most likely to ring you angry are the two they hear about first. A cancelled order says plainly that they have not been charged, and invites them to reply if it was not them. A refund says the money is on its way back and that banks take a few days to show it, which is the sentence that stops the second call. That one waits until you actually move the order to Refunded, so writing a credit slip while you work the figures out tells the customer nothing yet.
You know about a sale without watching the back office
One message to your own number the moment an order becomes real, naming the customer, how many items, the total and how they paid. It goes out on the first status that means money is on the way, so an abandoned checkout never wakes you at eleven at night, and an order that then moves on to paid and shipped still only wakes you once. List several numbers and whoever packs the order gets their own copy of it.
Whoever picks up the phone can see what was said
Open any order and the Messages block reads back what went to that customer, with Njiwa's message id, or the reason nothing went, including the plain note that this order had no phone number on its address. Those notes are private: the customer never sees them and they are never emailed out. Nobody on your side has to remember whether the shipping message really went, and nobody has to go and ask.
What your customer receives
These are the messages this integration sends out of the box, written out in full. You can change every word of them, but you do not have to: they are written to be sent as they are.
Hi Mary, thank you. Your payment for order #1043 came through and we are getting it ready. 2 x Tyre 195/65 R15 1 x Wheel balancing Total KSh 18,400 Riverside Hardware
Hi Mary, order #1043 is on its way to you. Thank you for shopping with Riverside Hardware.
Hi Mary, we have your order #1043 for KSh 18,400. We will let you know the moment your payment comes through. Riverside Hardware
New order #1043 on Riverside Hardware. Mary Wanjiku 3 item(s), KSh 18,400 Paid by M-Pesa
When each message goes out
Each of these is a separate switch, and every one of them is off until you turn it on. Installing this sends nothing to anybody.
| When it happens | Who hears about it |
|---|---|
| An order reaches Awaiting bank wire payment, Awaiting cheque payment or Awaiting cash on delivery validation | The customer, told you have the order and are waiting for the money. This is the one for bank transfer, cheque and cash on delivery, where the order exists before it is paid. |
| An order reaches Payment accepted, Remote payment accepted or On backorder (paid) | The customer, told the payment came through and you are getting the order ready. The wording that ships with it lists the items and the total. |
| An order reaches Shipped | The customer, told it is on its way. PrestaShop sends its own email at the same moment; this arrives where people actually look. |
| An order reaches Cancelled | The customer, told it was cancelled and that they were not charged, and invited to reply if that was not them. |
| An order reaches Refunded | The customer, told the money is on its way back and that banks take a few days to show it. Writing a credit slip on its own sends nothing. |
| An order reaches a status your payment or shipping module added | The customer, when that status is marked in Statuses as setting the order shipped or as setting it paid, and the shipped or the paid message goes out accordingly. Shipped is asked about first, because PrestaShop's Delivered status is both and by then the payment is old news. |
| The first of the awaiting, paid or shipped moments an order reaches | You, once per order, with a separate copy to every number in Your WhatsApp numbers. Not when the order row is created, which happens the moment somebody reaches the payment page and usually means nothing. |
Why it is safe on a shop that is trading
Every message is off until you turn it on. Installing the module sends nothing to anybody: there is no key saved, and all six messages arrive switched off, so the first one leaves only after somebody has turned it on deliberately.
Start on a test key. A key beginning sk_test_ checks and stores every message and delivers nothing to a phone. Everything else behaves exactly as it will on the live key, order notes and real message ids included, so you can walk a test order through every status before a customer is involved. Test connection and the order notes both say when a test key is in force.
It cannot block a checkout. Sending happens after the page has been delivered, and the hook that arranges it catches its own errors. A customer completes an order at the same speed whether this module is installed, switched off, or pointed at an address that does not answer. The one case where a page waits at all is the last resort described above, and even there the order was saved before any of it started.
A customer is never messaged twice for the same thing. The claim in the module's table covers the life of the order; the idempotency key covers a worker that runs twice inside 24 hours. A send that was interrupted and is now older than that window is deliberately not repeated, because a second copy to a customer is worse than a gap. Those are counted separately on the settings page, and the Njiwa console shows what was really sent.
It cannot message a WhatsApp group. Anything containing an @ is refused outright wherever it was typed, because a group address in the wrong box would otherwise message every person in that group from your own number.
No phone number is not an error. An order whose address carries no phone number gets a note saying no message was sent, and nothing else happens. A customer who did not leave a number is normal, not a fault to be raised at the checkout.
The master switch keeps your work, and fails loudly. Turning off Send WhatsApp messages stops every send while leaving your key, your numbers and all six wordings where they are. A message already queued when the switch went off is not silently dropped: the attempt is refused with a reason you can find on the order and in Advanced Parameters, Logs.
The key is not left lying about. It is encrypted with the shop's own encryption where PrestaShop provides it, and it is never written back into the settings page. On a shop where that encryption is unavailable it is stored as typed. PrestaShop keeps module settings in an ordinary database table either way, so a database dump is a database dump. If the shop is moved and its cookie key is regenerated, the saved key can no longer be read, and the page says so and asks you to paste it in again. Removing the module removes the key, the settings and the record of what was sent; the notes on your orders stay, because they belong to the order.
The two buttons are for the back office only. They act on a POST and never on a link, so nobody can be made to click one from somewhere else. The test message wording is fixed in the code: the operator supplies the recipient and nothing else, so the button cannot be turned into a way of sending arbitrary text from the shop's WhatsApp number. The cron address is guarded by a token of its own, and answers nothing at all without it.
The words are yours
Every message is plain text you can edit in your own admin, with placeholders in braces that are filled in from the order. These are the ones it knows:
{first_name}{last_name}{customer_name}{order_number}{order_total}{order_date}{order_status}{payment_method}{items}{item_count}{shop_name}{order_url}{admin_url}
A placeholder it does not know is removed before sending rather than posted to a customer. Empty wording sends nothing, which is how you turn one message off while leaving the event switched on.
Questions
Does it slow down the checkout?
No. The status change writes one row and returns; the message goes out after the page has been delivered. On hosting that can do neither of the two background routes, the page sends at most three messages itself within about ten seconds and leaves the rest queued, and even then the order was already saved. Njiwa being slow or unreachable cannot break an order.
Do I have to set up a cron job?
On most hosting, no, and there is nothing to configure. You need one when your server can neither hand back a finished page and carry on nor open a socket to itself. The settings page shows how many messages are waiting, and a number that never comes down is how you find out you are in that position. Point a cron job every five minutes at the address printed under The message to you, and keep that address private, since the token in it is the only thing stopping a stranger emptying your queue.
Will a customer get two messages if an order goes through the same status twice?
No. Every message is claimed in the module's table before it is attempted, keyed on the order, the moment and the number, so a status reached twice or a bulk update run again sends one message. If a send is interrupted and retried inside 24 hours, the idempotency key makes Njiwa replay its first answer instead of sending again.
Can I try the whole thing without messaging a real customer?
Yes, and it is the way in we recommend. Use the key beginning sk_test_. Every request is checked, every message is stored, real message ids come back and land in the order notes, and nothing reaches a phone. Place a test order, move it through your statuses, read the notes, then swap the key.
My shop has renamed its statuses, and my payment gateway added its own. Will this still fire?
It should, and the settings page shows you rather than leaving you to hope. It reads the status ids from your shop instead of assuming them, and prints what it found under Messages to your customers with your own names. A status your gateway or shipping module added counts too when it is marked in Statuses as setting the order shipped or as setting it paid.
The order note says it was sent but nothing arrived.
Read the note first. If it ends with the line about a test key, the message was stored and deliberately not delivered. If it carries a message id, Njiwa accepted it and the console shows what happened next, including whether the number was reachable. If the note carries a reason instead, that is the reason, and Advanced Parameters, Logs holds the same line with the error code beside it.
Do I need approval from Meta before I can use this?
Not for the Web rail, which works with a WhatsApp number you already own and needs no application. If you have been through the Cloud API route instead, that works too. The module neither knows nor cares which rail a number is on: it posts to the same endpoint, and Njiwa sends over whichever rail that number uses.
Has this been run on a real PrestaShop shop?
Not yet. The code is complete and has been read closely, and the two problems that review found, retries that spent all three attempts in one second and a queue that could sit still on hosting with no background route at all, are fixed. What is missing is an install on a live shop taking real orders. Run it on a test key first, and tell us what breaks.
What it does not do
Said plainly, so you are not surprised later
- It has not been installed on a live PrestaShop shop. The code is finished and the review it has had was a careful reading rather than an order placed on a real storefront, so treat the first week as a rehearsal and start on a test key.
- It does not receive replies. Incoming WhatsApp and delivery receipts arrive as webhooks, and verifying one needs that number's signing secret, which the console does not yet show. Until it does, a receiving feature could not prove a request really came from Njiwa, so there is not one.
- It does not keep its own copy of your messages. Njiwa already stores every message, its status and its failure reason. What the module keeps is a list of what it was asked to send, so that it never asks twice.
- It sends plain text only. There is no attaching the invoice PDF from this module; put {order_url} in the wording and the customer opens their own order. Njiwa's API does send files, so a developer can do it directly.
- The note on the order records that Njiwa accepted a message, not that a phone displayed it. Delivery and read status live in the Njiwa console.
- There is one wording per message for the whole shop. It is not translated per language, and there is no per product, per country or per customer group variation, so a shop selling in three languages sends the same text to all of them.
- A credit slip on its own sends nothing. The refund message goes out when the order actually reaches the Refunded status.
- There is no digest. Every order that becomes real sends its own alert to every number on your list, so a shop taking hundreds of orders a day should probably leave that one off.
- The retry ladder is fixed at three attempts, five minutes and then twenty, and is not configurable. After that the message waits for you to press the button on the settings page; nothing tries it again on its own.
- {admin_url} is empty until somebody has opened the settings page in the back office, and it only ever links to the front door of the back office rather than to the order, because a link to one order carries the sign-in token of whichever employee last opened that page.
From here on, it is the install
That is everything a person deciding whether to use this needs to know. What follows is for whoever puts it on your shop, and it takes about ten minutes. If that is not you, this is the point to send them the link.
What it needs
- PrestaShop 1.7 or 8.x. The module names 1.7.0.0 as its floor and your own shop version as its ceiling, so it installs on both without a compatibility warning.
- PHP 7.4 or newer. The floor is low on purpose, because plenty of shops that would benefit from this are on hosting that has not moved.
- The PHP cURL extension. It is the only HTTP client the module uses, and it is on almost every host that can run a shop. Where it is missing, the module says so in plain words instead of failing quietly.
- MySQL, which PrestaShop already needs. The module creates one table for its own queue when it is installed and drops it when it is removed.
- Outbound HTTPS from your server to njiwa.upeo.ai. The certificate is verified and redirects are deliberately not followed, so a proxy that redirects this traffic is reported rather than silently trusted.
- The module folder has to be called njiwa, because PrestaShop finds a module by its folder name.
- A Njiwa account and an API key. A test key is enough to install, configure and rehearse the whole thing.
- A cron job every five minutes, but only on hosting that can neither finish a response and keep working nor open a socket to itself. The settings page tells you when your shop is in that position.
Install it
- Clone it into a folder named njiwa, then zip that folder. The name matters: PrestaShop takes a module's technical name from its folder, and njiwa.php has to sit one level inside the zip rather than at the top of it, which is also why GitHub's own Download ZIP needs renaming first. If you have SFTP instead, copy the folder into modules/ on the server, named njiwa, and skip the zip.
git clone https://github.com/Upeosoft-Limited/njiwa-prestashop njiwa zip -r njiwa.zip njiwa -x 'njiwa/.git/*' - In the back office open Modules, Module Manager, Upload a module, and choose the zip. Skip this step if you copied the folder in yourself.
- Press Install on Njiwa WhatsApp. Installing registers one hook, creates the module's message table, and writes the settings with every message switched off.
- Press Configure. That page is the whole module, and opening it in the back office is also what teaches {admin_url} where your back office lives.
Set it up
- Sign in at console.upeo.ai, open API keys, and create a key. Copy the test key, the one beginning sk_test_. The console shows a key once and keeps only its fingerprint, so a lost key is replaced rather than recovered.
- Paste it into API key and press Save. The box is never filled in again afterwards; the page tells you the last four characters of the key it holds, and an empty box means keep that key. Leave Njiwa address exactly as it is unless you were given an address of your own.
- Press Test connection. It asks Njiwa which WhatsApp numbers your account really has, lists each one with its state, and says plainly when the key is a test key. It sends nothing. Both buttons save the page first, so what you test is what your shop will use.
- If your account has more than one number, put the one these messages should come from into Send from, digits only in full international form such as 254712345678. Leave it empty to use the number marked default. Test connection warns you when what you typed matches no number on the account, because every message would then be refused.
- Put your own WhatsApp number into Your WhatsApp numbers, under The message to you, and save. Several are allowed, separated by commas, and each one gets its own copy. When you save, anything in that box that is not a usable number is reported, together with the list of numbers that will actually be messaged.
- Type a number into Test message to and press Send test message. One fixed message goes there and nowhere near a customer, and the answer gives you Njiwa's message id or the exact reason it failed. That box is not saved, and the button allows one test every thirty seconds.
- Read the list under Messages to your customers. It names the statuses in your own shop that each message is attached to, in your own language, and says when a status does not exist here at all, which means that message can never fire.
- Turn on the messages you want and edit the wording under each one. Every message ships with wording that works unedited, so turning one on is one click rather than one click and a writing exercise.
- Place a test order and move it through the statuses you turned on. Open the order and read the Messages block: each note records what went where and Njiwa's message id, or why nothing went.
- When it all reads right, paste your sk_live_ key over the test one and save. From that point the messages reach real phones and count against your plan.
How it works
The module registers one hook, actionOrderStatusPostUpdate, which PrestaShop fires after an order's status has genuinely changed. That is the whole reason it is the right hook: it is not "the order was saved", so editing an address never messages anybody, and it fires from the checkout, from the order page, from a bulk update and from every payment module alike. The status it just reached is matched against the ids your shop holds for bank wire, cheque, cash on delivery, payment accepted, shipped, cancelled and refunded, and any other status is still recognised when it is marked as setting the order shipped or paid.
Inside the hook the module does as little as it can, because that code is often running in a customer's own checkout: it reads a few settings, works out one phone number from the order's invoice address, or its delivery address when there is no invoice one, and writes one row. That number is reduced to its digits, so the spaces and brackets people type are dropped: a local number then gains the dialling code PrestaShop already holds for that country, in place of the 0 you only dial at home, while a number written in full, with a + in front or the 00 much of the world dials out with, keeps the country code the customer wrote, with those dial out zeros dropped and no second code put in front of it. The row is an INSERT IGNORE against a unique key of shop, order, moment and recipient, so a second attempt at the same message collides with the first and stops there. Anything that goes wrong while arranging a message is caught and written to the log, because an order must never fail to change status over a WhatsApp message.
The queue is emptied after the response has gone out. Where PHP-FPM can hand the finished page to the web server and carry on, that is what happens and the customer pays nothing for it. Where it cannot, the shop opens a socket to its own cron address, writes one line and hangs up without waiting. Where neither is possible, and some hardened shared hosting allows neither, the page that changed the status sends up to three messages itself inside a ten second budget and leaves the rest in the queue. That last case is the one where a real cron job every five minutes, on the token protected address the settings page prints, stops being optional.
The worker renders the wording against the order, trims it to 4000 characters so it fits what WhatsApp accepts, and posts it to /v1/messages with your key, the idempotency key and a User-Agent naming the module and its version. The request gives up after 20 seconds, and 10 if the host will not answer at all, so nothing can hold a worker open. A network failure is not treated as a failed send: the message was never accepted, so it is tried again, three times in all, waiting five minutes and then twenty between attempts, and so is a rate limit or a fault on our side. A refusal Njiwa can explain, a number it will not accept or a key that is not allowed, is not retried, because asking again would only be told the same thing. Either way the reason is written on the order, and the settings page counts what it gave up on and offers to put it all back in the queue.
Get it
The code is public, at https://github.com/Upeosoft-Limited/njiwa-prestashop.
Clone it, read it, and see every line that will run on your shop before you
install it. Inside is a README written for the person who runs the shop
rather than for a developer. It is developed at
packages/njiwa-prestashop in the Njiwa repository and published
from there, so the two never drift apart. The API behind it is documented at
https://docs.njiwa.upeo.ai/.
You need a Njiwa account for the key. A trial gives you a live key straight away and a test key in one click, so you can install this, rehearse a whole order and only then decide.