Njiwa for Magento 2

WhatsApp your customers when their order is placed, paid, shipped, cancelled or refunded, and get a message yourself when one comes in.

Start a 7 day trial See what your customer gets

What changes for your business

The question your desk answers all day, answered already

Most of what a shop answers all day is one question, asked by people who have no way of knowing the answer. Every step a Magento order really takes, the order landing, the invoice being paid, the shipment going out, the credit memo, is one your customer now hears about the moment it happens, on the app already open on their phone. Magento sends its own email at the same moment; this arrives where people actually read. You are not writing those replies any more, and neither is whoever you pay to write them.

You know an order is real without opening Magento

One message to your own number as an order is placed, with the customer's name, how many items, the total and how they paid. List several numbers and everybody on the list gets their own copy, so the person who packs hears about it at the same time you do. It goes on the order itself rather than on the checkout before it, so an abandoned checkout never buzzes your phone. There is no link in it: Magento cannot build an admin link that works for anybody but the person who made it, so you get the order number and open the order yourself rather than landing on a login screen.

Bad news lands better when it comes from you

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 a reply if it was not them. A credit memo says the money is coming back and that banks take a few days to show it, which is the sentence that stops the second phone call. Either way the order's own Comments History records what was said and when, so whoever picks the phone up next is not guessing.

One Magento, several shops, each in its own voice

If you run more than one website or store view, a second brand or a store selling into another country, each of them keeps its own wording, its own events, its own sending number and its own Njiwa key if you want one. Every message is composed with the settings of the store the order was actually placed in, so a customer of one brand is never written to in the name of another.

Changing what it says does not need a release

The wording is a box of plain text in Stores, Configuration, Sales, Njiwa WhatsApp, saved like any other Magento setting: no template file, no theme, no deployment, no developer. Every event ships with wording that works unedited, so you can switch one on today and rewrite it next week. Clearing a box stops that one message and keeps the rest, and turning the whole thing off stops it now rather than after the queue has drained, because each message is checked against the settings again on the way out.

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

09:24
Payment lands

Hi Mary, order #1043 is on its way to you. Thank you for shopping with Riverside Hardware.

11:06
You ship the order

Hi Mary, we have your order #1043 for KSh 18,400. We will let you know the moment your payment comes through. Riverside Hardware

13:41
Bank transfer, still waiting

Hi Mary, we have refunded KSh 18,400 for order #1043. Banks take a few days to show it. Riverside Hardware

15:18
You refund an order

New order #1043 on Riverside Hardware. Mary Wanjiku 3 item(s), KSh 18,400 Paid by M-Pesa

16:52
A new order, to you

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.

What Magento 2 sends, and to whom
When it happens Who hears about it
An order is placedThe customer, told you have the order and will say the moment the payment comes through. This is the one that earns its place on bank transfer and cash on delivery, where the order sits waiting for money. Magento raises it once, inside the checkout, as the order is accepted.
An invoice is paidThe customer, told the payment came through and you are getting the order ready. The wording that ships lists the items and the total. A shop taking card payment at the checkout sees this and Order placed within a second of each other, and most shops want this one rather than both.
A shipment is createdThe customer, told it is on its way. An order shipped in two parcels messages once, on the first, and saving a shipment again to add a tracking number messages nobody.
An order is cancelledThe customer, told it was cancelled and that they were not charged, and invited to reply if that was not them. It fires whether the cancellation came from the admin, from the customer, or from a payment that was never completed.
A credit memo is createdThe customer, told the money is coming back and that banks take a few days to show it. Sent on the first credit memo for an order, and not again.
The same moment an order is placedYou, once per order, on every number in Your WhatsApp numbers. It goes on the order itself rather than on the checkout before it, because somebody who reached the payment page may never come back, and an abandoned checkout is not a sale to be told about.

Why it is safe on a shop that is trading

Every event is off until you turn it on. A fresh install has no key to send with and all six events switched off, so it sends nothing to anybody. The master switch itself arrives on, on purpose: a switch that arrived off would only mean that somebody who pasted a key and turned an event on still got nothing, and found out from a line in a log.

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 comments and message ids included, so you can rehearse a real order before a customer is ever involved.

It cannot block a checkout. Messages are queued rather than sent inline, and every observer 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.

A customer is never messaged twice for the same thing. The claim row in upeo_njiwa_message outlives everything: it is taken against the store, the order number, the event and the number it is going to, and a unique key on those four means a second attempt at the same message collides with it and stops there. That is what stops a second message a week later when a shipment is saved again to add a tracking number, or when a shop replays an order. The number is part of that key, so an alert to three of your own numbers stays three messages rather than collapsing into one.

A staging site cannot suppress the live shop's messages. The Idempotency-Key sent with each message covers the twenty-four hours around a job that runs twice, and it folds in that store's own web address. A staging site restored from production carries production's order numbers, and two shops can share one Njiwa account; without the address in it, order 1043 on the copy and order 1043 on the live shop would look like the same message and the second would be quietly dropped. They are two different keys, so both go.

A placeholder that does not exist is never sent to a customer. Wording that asks for {order_no}, which is a typo rather than one of the placeholders this module knows, has that word taken out on the way past instead of arriving on a phone in braces, and a line naming the placeholder and the order goes into var/log/njiwa.log so you can find it.

It cannot message a WhatsApp group. A group is addressed as something like 12036304@g.us, and Njiwa would post to it, so anything carrying an @ is refused outright rather than having its digits picked out. That holds for a number read off an order, for the numbers you type in as your own, and for the number typed into the test message box.

No phone number is not an error. An order from a customer who left no usable number sends nothing and complains about nothing. It is ordinary, not a fault to be raised at the checkout.

Switching off fails loudly rather than silently. With the master switch on No, an attempt to send is an error with a line in var/log/njiwa.log saying exactly that, and the Test connection button says it in a sentence. Silence that looks like a working shop is the thing worth avoiding here.

The log is this module's own file. Everything it writes goes to var/log/njiwa.log rather than into Magento's system.log with the rest of the shop: a line for every message that went, naming the order, the event and the number, and a line for every one that did not, with the reason and its error code. One file is the whole answer to why a customer was not messaged, and you can read it, or send it to us, without sending the log of your whole shop.

Both buttons are for administrators only. They are guarded by the same permission as the settings page, Upeo_Njiwa::config, they are POST only, and Magento checks its form key on every admin POST, so a page on another site cannot press them. The test message wording is fixed in the code: the operator supplies the recipient and nothing else, so the button cannot become a way of sending arbitrary WhatsApp messages from the shop's own number.

The master switch keeps your work. Set it to No and every send stops while your key, your numbers and all six wordings stay where they are. Order comments stay too, in every case, because they are a record of what was sent and they belong to the order.

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}

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 observer writes the message, hands it to the queue and returns; the consumer sends it after the customer has been sent on their way. Njiwa being slow or unreachable cannot hold up an order, and anything that goes wrong while arranging a message is logged rather than raised at the checkout.

What has to be running for messages to go out?

Something that runs Magento's message queue consumers, which on nearly every shop is Magento's own cron. It starts the consumer upeo.njiwa.send every minute, exactly as it starts the one that sends your order emails. If nothing is running consumers, nothing is sent and nothing is lost: the messages sit in the queue until something drains them.

Can I try the whole thing without messaging a real customer?

Yes, and it is the recommended way in. Use the key that begins sk_test_. Every request is checked, every message is stored, the order comments are written with real message ids, and nothing reaches a phone. Place a test order, invoice it, ship it, read the comments, then swap the key.

Will a customer be messaged twice if I save a shipment again?

No. Saving a shipment to add a tracking number raises the same Magento event a second time, and the claim row for that order, that event and that number is already taken, so the second attempt stops there. An order shipped in two parcels messages the customer once, on the first shipment.

It says nothing arrived. Where do I look?

The order's Comments History first. If the note 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. If it carries a reason instead, that is the reason, and var/log/njiwa.log holds the same line with the error code next to it. If there is no note at all, nothing was sent: either that event is switched off or its wording is empty, or the customer left no usable number, or the message is still in the queue because nothing is running the consumer.

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 does not know or care 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 Magento shop?

Not yet. It is complete, it follows Magento's own APIs for observers, queues, configuration and admin controllers, and it has been reviewed line by line against them. It has not been installed on a running Magento, no real order has been through it, and it ships no automated tests. Start with a test key, place a test order, read the order comments, and tell us what you find.

How do I remove it?

bin/magento module:disable Upeo_Njiwa stops it and keeps everything. Deleting the files instead leaves your key encrypted in core_config_data and the upeo_njiwa_message table where it is; this module ships no uninstall script, so bin/magento module:uninstall Upeo_Njiwa is what removes those, and that only works for a module installed by Composer. Order comments stay either way.

What it does not do

Said plainly, so you are not surprised later

  • It has not been installed on a running Magento. The code is finished and has been read closely against Magento's own APIs, and neither of those is the same thing as a real order from a real shop. It also ships no automated tests of its own.
  • It does not receive replies. Incoming WhatsApp arrives as a webhook, 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 a copy of your messages. The table it keeps records who was messaged about which order and what became of it. The messages themselves live in the Njiwa console, where they are already searchable, and a second copy is a second thing to keep in step.
  • There is no screen for that table either. The record a merchant reads is the order's Comments History and var/log/njiwa.log; nothing is added to the admin menu.
  • It sends plain text only. There is no attaching the invoice PDF from this module. Njiwa's API sends files, so a developer can do it directly.
  • There is no {order_url} and no {admin_url}. Magento cannot produce either honestly: the customer's own order page needs a logged-in customer, which a guest checkout does not have, and an admin URL carries a secret key tied to the session of whoever generated it. A link that lands somebody on a login screen is worse than no link.
  • {order_total} on the refund message is the total of the order, not the amount refunded, so the wording that ships is right for a full refund and overstates a partial one. If you refund parts of orders, say so in the wording rather than quoting a figure.
  • A refusal is not retried. If Njiwa says no, or cannot be reached at that moment, the reason is written on the order and in the log and the module does not try that message again by itself. The claim row stays, so nothing goes out later on its own.
  • The order comment records that Njiwa accepted a message, not that a phone displayed it. Delivery and read status live in the Njiwa console.
  • It does not run campaigns. Bulk sending to past customers is what the Njiwa console is for, on Business plans and above.
  • There is no digest. Every order that is real gets its own message to you, so a shop taking hundreds of orders a day should probably leave that particular event off.
  • A local number is read against your own sending number's country, not against the country on the order, because Magento ships no list of dialling codes and a copy of that list inside this module would go stale. If you sell across borders, store customer numbers in full international form.

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

  • Magento Open Source or Adobe Commerce 2.4. The one place the module could have used a newer-only feature, its own log file, is written the older way instead, so it does not need a recent point release.
  • PHP 7.4 or newer with the JSON extension, which is what its composer.json asks for. The floor is low on purpose, because plenty of shops that would benefit from this are on hosting that has not moved.
  • Access to bin/magento. Installing it means module:enable and setup:upgrade, and setup:upgrade is what creates the one table this module has.
  • Something that runs message queue consumers, which on nearly every shop means Magento's own cron. Nothing is sent from the checkout: a consumer called upeo.njiwa.send does the sending, and cron starts it every minute, the same machinery that sends your order emails.
  • No extra queue software. The module names no queue connection, so it uses whatever your installation already uses: the database on a plain install, or RabbitMQ if you have one.
  • A Njiwa account and an API key. A test key is enough to install, configure and rehearse the whole thing.
  • Outbound HTTPS from your server to njiwa.upeo.ai. Requests go through Magento's own HTTP client, so a shop behind a proxy or a host with opinions about outbound traffic treats this module like the rest of Magento.

Install it

  1. Install the code. Composer is the tidy way and the one that lets you remove it again later, but the module is not on Packagist, so Composer has to be told where it lives first. The repository is public, so that needs no token, and Composer reads versions from git tags, so until a release is tagged ask for the branch by name. Cloning the repository and copying it to app/code/Upeo/Njiwa works just as well and asks nothing of Composer.
    Terminal, 2 lines
    composer config repositories.njiwa vcs https://github.com/Upeosoft-Limited/njiwa-magento
    composer require upeo/module-njiwa dev-main
  2. Enable the module.
    Terminal
    bin/magento module:enable Upeo_Njiwa
  3. Run the upgrade. This creates the table upeo_njiwa_message, which is what stops a customer being messaged twice, so the module is not finished installing until it exists.
    Terminal
    bin/magento setup:upgrade
  4. Compile, on a production mode installation only.
    Terminal
    bin/magento setup:di:compile
  5. Flush the cache.
    Terminal
    bin/magento cache:flush
  6. Check the consumer is registered. upeo.njiwa.send should be in the list, alongside Magento's own consumers.
    Terminal
    bin/magento queue:consumers:list
  7. Make sure something is running that consumer. If Magento's cron is running there is nothing to do. If cron is not installed, install it, then check that bin/magento cron:run is in the web user's crontab.
    Terminal
    bin/magento cron:install
  8. If your host runs consumers itself as long lived processes instead, which Adobe Commerce cloud does by setting cron_run to false under cron_consumers_runner in app/etc/env.php, add this to whatever supervises them. If that file also lists consumers individually under consumers, upeo.njiwa.send has to be in the list or it is the one that never starts.
    Terminal
    bin/magento queue:consumers:start upeo.njiwa.send
  9. Open Stores, Configuration, Sales, Njiwa WhatsApp. If a colleague cannot see that section, their admin role is missing Njiwa WhatsApp Section under System, Permissions, User Roles, Role Resources.

Set it up

  1. 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.
  2. Paste it into API key and press Save Config. Magento stores it encrypted, with the rest of your credentials. Leave Njiwa address exactly as it is unless you were given an address of your own.
  3. Press Test connection. It asks Njiwa which numbers this key can send from, lists each one with whether it is linked yet, and says plainly when the key is a test key. It sends nothing. Both buttons read the settings as they are saved rather than as they are on screen, so save first and then check.
  4. 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 in the console. Test connection warns you when Send from names a number the account does not have, which is the one setting that looks right on the page and then refuses every message.
  5. Type your own number into the box beside Send a test message and press the button. One message goes out, with wording fixed in the code, and the answer carries Njiwa's message id or the exact reason it failed. It refuses a second test within ten seconds of the last, so a stuck key cannot put a row of them on somebody's phone.
  6. Turn on the events you want under Messages to your customers, and edit the wording under each one. Every event ships with wording that works unedited, so turning one on is one click rather than one click and a writing exercise. Clearing a wording box is how you stop one message without touching the switch above it.
  7. For the message to yourself, set Tell me about new orders to Yes and put your own number into Your WhatsApp numbers. Several are allowed, separated by commas, and everybody listed gets their own copy.
  8. Place a test order and take it through the moments you switched on: invoice it, ship it, cancel it, refund it. Then read the order's Comments History and var/log/njiwa.log. Both say what was sent, to whom, and Njiwa's own id for it, and a test send is labelled Test key, so nothing reached WhatsApp.
  9. If you sell through more than one website or store view, set the events and the wording at that scope too. Every setting can be set per website and per store view, and each message is composed with the settings of the store the order was actually placed in.
  10. When it all reads right, replace the sk_test_ key with your sk_live_ key and save. From that point the messages reach real phones and count against your plan.

How it works

The module watches five specific Magento events, one per thing that actually happens to an order: sales_order_place_after, sales_order_invoice_pay, sales_order_shipment_save_after, order_cancel_after and sales_order_creditmemo_save_after. It deliberately does not watch sales_order_save_after, which fires whenever anything at all touches an order and would message a customer because somebody corrected a delivery address.

When one of those fires, the module checks the master switch, whether a key is saved, whether that event is switched on and whether there is wording for it, reads the billing telephone and falls back to the shipping one, and renders the message there and then. {order_total} comes out in the currency the customer was actually charged in rather than the one your admin happens to display, and {order_date} in the store's own timezone rather than the server's, so nobody is told they ordered at three in the morning. It is finished at that moment rather than in the worker because sales_order_place_after runs before the order row has been written, so a worker told only the order number could arrive before the order existed. The finished text, the recipients and enough of the order to write the outcome back onto it are published to upeo.njiwa.send.

The consumer is what talks to Njiwa. Magento's cron starts it every minute and it stops after a batch, the way the rest of Magento runs its consumers. It reads the settings again rather than trusting the ones that were in force when the message was queued, so somebody who switches the module off gets it off now and not after the queue has drained. Then it claims its row, and posts to /v1/messages with your key, an Idempotency-Key, a twenty second timeout and a User-Agent naming the module and the version in its own etc/module.xml.

Njiwa's answer becomes the note on the order and the status of the row. A refusal arrives with a stable machine readable code and a sentence a person can act on; the sentence goes on the order and both go to the log. That refusal is not retried, because Njiwa would say the same thing again. Nothing in the consumer throws: a handler that threw would mark the queue message as an error and write a stack trace where a merchant will never look.

Get it

The code is public, at https://github.com/Upeosoft-Limited/njiwa-magento. 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-magento 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.

Start a 7 day trial All nine integrations