Njiwa for Shopware 6

WhatsApp your customers when their order is 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

Your customers stop chasing you for news

Most of what a shop's inbox and phone carry all day is one question, asked by people who have no way of knowing the answer. Every state you move an order through, a payment marked paid, a delivery marked shipped, an order cancelled, is one the customer now hears about the moment you do it, on the app already open on their phone. There is no second step to remember and no separate screen to visit: the message follows the work you were doing in the administration anyway. A delivery that is only partly shipped deliberately says nothing, because telling somebody their order is on its way when half of it is still on the shelf makes the phone ring rather than stopping it.

It arrives where your email does not

Shopware sends its own confirmation to the address somebody typed at the checkout, and that address ends up in a promotions tab, behind a spam filter, or nowhere at all when a letter was mistyped. The phone number on the order is the one the customer gave you to be reached on. This is the same news, sent there instead, and it is read on the phone they are already holding.

You know about a sale without watching the administration

One message to your own number the moment an order is placed, carrying the customer's name, how many items, the total, how they paid and a link that opens that order in the administration. Everybody who needs to know can be on that list, so the person who packs and the person who owns the shop hear at the same time. It goes when the order is placed, which is the first moment an order is real in Shopware. What exists before that is a cart, so a checkout somebody walked away from never wakes you at eleven at night.

The awkward moments are the ones it earns its keep on

The two moments a customer is most likely to ring you angry are the two they now hear about first. A cancelled order says so plainly 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 phone call. If somebody disputes it months later anyway, the plugin has kept its own row for every message it sent, in the shop's own database, saying which number it went to, for which order, and what Njiwa said back. Shopware has no order note stream to write that into, so that row is the record, and it is still there long after the log files have rotated away.

Two names on one Shopware can say two different things

The wording of every event, the WhatsApp number the messages come from and the key itself are all read at the sales channel the order was placed in. A business selling under two names from one installation can sound like both, from two different numbers, without running the shop twice. A shop with one sales channel never has to think about it: fill the settings in once and every order uses them.

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 mark it shipped

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

New order #1043 on Riverside Hardware. Mary Wanjiku 3 item(s), KSh 18,400 Paid by M-Pesa https://riverside.example/admin#/sw/order/detail/018f3a2b9c7d4e5f8a1b2c3d4e5f6a7b

15:18
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 Shopware 6 sends, and to whom
When it happens Who hears about it
The order is placedThe customer, told you have the order and are waiting for the payment. This is the one for bank transfer, invoice and cash on delivery, where the order is real long before the money is.
A payment on the order moves to paidThe customer, told the payment came through and you are getting the order ready. The default wording lists the items and the total. A partly paid payment is deliberately not this.
A delivery on the order moves to shippedThe customer, told it is on its way. A partly shipped delivery is deliberately not this: telling somebody their order has shipped when half of it has is a customer waiting at the door.
The order itself moves to cancelledThe customer, told it has been cancelled and invited to reply if that was not them. A cancelled payment or a cancelled delivery is not this, because those mean something else.
A payment on the order moves to refundedThe customer, told the money is on its way back and that banks take a few days to show it.
The order is placedYou, once per order, on every number listed in Your WhatsApp numbers. It goes when the order is placed, which in Shopware is the first moment an order is real: what exists before that is a cart, and a cart is somebody who reached the payment page and may never come back.

Why it is safe on a shop that is trading

Every event is off until you turn it on. Installing and activating the plugin sends nothing to anybody. No key is saved, and all six events arrive switched off, so the first message leaves only after somebody has ticked a box on purpose and saved a key.

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, message ids included, and the log line and the check screen both say out loud that the key was a test one, so you can rehearse a real order before a customer is ever involved.

It cannot block a checkout. Messages are queued, never sent inline, and every listener wraps its own work in a catch. A customer completes an order at the same speed whether this plugin is installed, switched off, or pointed at an address that does not answer.

A customer is never messaged twice for the same thing. The claimed row is a single insert that either succeeds or collides, which is a race no amount of checking then writing in PHP can win, and the idempotency key covers a job that runs twice inside a day. The recipient is part of both, so an alert going to three of your own numbers stays three messages rather than collapsing into one.

It cannot message a WhatsApp group. A value ending @g.us is a group, and Njiwa will post to it, so anything carrying an @ is refused outright rather than having its digits picked out. Nothing shorter than 7 digits or longer than 15 is accepted either, so an order reference typed into a phone field is not mistaken for a number.

No phone number is not an error. The billing address is tried first, then the delivery address, because a gift order often has the only reachable number there. If neither has one, nothing is sent and nothing is complained about. A customer who did not leave a number is normal.

The master switch keeps your work. Turning off Send WhatsApp messages stops every send while leaving your key, your numbers and all six templates exactly where they are, and orders carry on precisely as before. Anything that does reach Njiwa while it is off fails loudly with a line saying which switch is off, rather than a silence that looks exactly like a working shop.

Both checks need the permission they are checking. The buttons post to /api/_action/upeo-njiwa/test-connection and /api/_action/upeo-njiwa/send-test-message, both POST only and both behind the administration's own session, and the privilege is enforced on the endpoint rather than by hiding a menu entry: Test connection asks for system_config:read and Send test message for system_config:update. They answer curl exactly as they answer the buttons, so an installer working over SSH and a merchant clicking in the administration are checking the same thing, under the same permissions. The test wording is fixed in the code, so the operator supplies a recipient and nothing else and this cannot become a way of sending arbitrary messages from the shop's own number.

Your API key sits where Shopware puts plugin settings. That is the system_config table, which Shopware does not encrypt, so treat a database dump as something that contains this key. If one gets out, replace the key in the Njiwa console rather than only changing it here.

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 listener does two cached settings lookups and puts a message on Shopware's queue, then the customer is on their way. Reading the order, writing the wording and talking to Njiwa all happen in a worker afterwards, and an error while arranging a message is logged rather than raised at the checkout.

Do I really need a queue worker running?

Yes, and it is the one thing to settle before turning any event on. Every message goes onto Shopware's own queue, so if nothing is consuming it, nothing is ever sent. Most shops already run bin/console messenger:consume under a supervisor, or have the admin worker turned on. If yours does not, fix that first, because the plugin will look switched on and quietly send nothing.

What happens if a customer has no phone number?

Nothing is sent, and nothing is treated as broken. The billing address is checked first and the delivery address second, and if neither carries a usable number the message is skipped. The order is unaffected.

Will a customer get two messages if I move a delivery back and forth?

No. A row is claimed for that order, that event and that recipient before anything is sent, and a second attempt collides with it and stops, whether it comes a second later from another worker or a week later because somebody was correcting a tracking code. Inside 24 hours the idempotency key catches it as well, and Njiwa replays its first answer instead of sending again.

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

Yes, and it is the recommended way in. Use the key beginning sk_test_. Every request is checked, every message is stored, real message ids come back, and nothing reaches a phone. Place a test order, move it through the states you turned on, read the njiwa lines in var/log, then swap the key.

Can each sales channel have its own key and wording?

Yes. Every setting is stored per sales channel by Shopware's own mechanism and read at the channel the order was placed in, so two shops sharing one installation can send different words from different numbers. The check screen has the same picker, so you can test one channel's settings rather than the shop wide ones.

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 plugin 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 Shopware shop?

Not yet. The code is complete, two blockers found in review have been fixed, and the event classes and the routing loader behind them were checked against the 6.5.8.8 and 6.6.10.0 source, because both of those fail silently when they are wrong. The administration screen has never been built and clicked, and no real order has been through any of it. Start with a test key, place a test order, read the log, and tell us what you find.

What it does not do

Said plainly, so you are not surprised later

  • It has not been installed on a running Shopware. The code is finished and has been read closely against the 6.5 and 6.6 source, and neither of those is the same thing as a real order from a real shop. The administration module in particular has never been built or clicked. The tests that do exist cover the wording and the phone numbers, need no Shopware and no database, and check that the default wording in the settings form and in the code have not drifted apart.
  • 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 its own copy of the messages. Njiwa already stores every message, its status and its failure reason. What the plugin keeps is the fact that one was asked for, which is what stops it being asked for twice.
  • There is no screen for that table. The check screen under Settings holds the two checks and nothing else, so reading what was sent means the njiwa channel in var/log or a query against upeo_njiwa_message.
  • It sends plain text only. There is no attaching the invoice PDF to the message from this plugin; put {order_url} in the wording instead and the customer opens their own order. Njiwa's API sends files, so a developer can do it directly.
  • Partly paid and partly shipped send nothing, on purpose. Only a payment that reaches paid or refunded, a delivery that reaches shipped, and an order that reaches cancelled are treated as worth telling a customer about.
  • There is one wording per event per sales channel. No per product, per category, per country or per language wording, and no way to turn an event on for some orders and off for others.
  • {admin_url} is built from your APP_URL and assumes the administration is still at /admin. A shop that has moved it should take that placeholder out of its wording rather than trust the guess.
  • There is no digest. Every order gets its own alert to you, so a shop taking hundreds of orders a day should probably leave that particular event off.
  • It does not run campaigns. Bulk sending to past customers is what the Njiwa console is for, on Business plans and above.
  • A local number is read against your own sending number's country, not against the country on the order, because a Shopware country carries no dialling code to read it against. 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

  • Shopware 6.5 or 6.6. The plugin asks composer for shopware/core ~6.5.0 or ~6.6.0, and the event classes and the routing loader it depends on were checked against the 6.5.8.8 and 6.6.10.0 source, because both fail silently rather than loudly when they are wrong.
  • PHP 8.1 or newer, with the JSON extension. That is this plugin's own floor, and Shopware's floor for the version you are running is the one that decides in practice, because it is the higher of the two.
  • A message queue worker that is actually consuming. Every message is handed to Shopware's own queue so that nothing is sent while a customer is standing at the checkout, so a shop with nothing running bin/console messenger:consume, and without the admin worker turned on, sends nothing at all.
  • Room for one table. The plugin's migration creates upeo_njiwa_message in the shop's own database and nothing else.
  • Outbound HTTPS from the shop to njiwa.upeo.ai. Requests go through the HTTP client Shopware already configures, so a host that proxies or filters outbound traffic treats this plugin like every other one.
  • A Njiwa account and an API key. A test key is enough to install, configure and rehearse the whole thing.
  • A build of the administration if you want the check screen. Messages send either way; without the rebuild there is no Settings entry to press the two check buttons from.

Install it

  1. From the root of the shop, tell Composer where the plugin lives and then ask for it. 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 it to custom/plugins/UpeoNjiwa works too; carry on at the next step.
    Terminal, 2 lines
    composer config repositories.njiwa vcs https://github.com/Upeosoft-Limited/njiwa-shopware
    composer require upeo/njiwa-shopware dev-main
  2. Let Shopware notice that there is a new plugin to know about.
    Terminal
    bin/console plugin:refresh
  3. Install and activate it. Installing runs the migration that creates the upeo_njiwa_message table, and sends nothing to anybody: every event arrives switched off and there is no API key saved yet.
    Terminal
    bin/console plugin:install --activate UpeoNjiwa
  4. Clear the cache.
    Terminal
    bin/console cache:clear
  5. Run bin/build-administration.sh, which rebuilds the administration so that Settings, Njiwa WhatsApp appears. Skip it and every message still sends; you just have no buttons to check the setup with, and have to call the two endpoints with curl instead.
  6. Check that something is consuming the queue, either this under a supervisor or the admin worker turned on. If nothing does, nothing is ever sent, and that is the one thing to fix before turning any event on.
    Terminal
    bin/console messenger:consume

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. In the administration, open Extensions, My extensions, Njiwa WhatsApp, and choose Configure from the three dot menu.
  3. Paste the key into API key and save. Leave Njiwa address exactly as it is unless you were given an address of your own. If different sales channels should behave differently, use the picker at the top of the page and save each one separately.
  4. Open Settings, Njiwa WhatsApp and press Test connection. It lists the WhatsApp numbers your Njiwa account really has, with the state of each and which is default, it says plainly when the key is a test key, and it sends nothing. Leave the sales channel box empty to check the shop wide settings, or pick one to check what that channel has saved.
  5. 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 and with no leading zero. Leave it empty to use the number marked default in the console. Test connection tells you when what you typed is not a number on the account, because every message would then be refused.
  6. Press Send test message with your own number in the box. One fixed message goes to that number and nowhere else, and whatever comes back, including a refusal and the reason for it, is printed on the page. On a live key that is a real message that costs real money, so it is limited to five a minute.
  7. Tick the events you want and edit the wording underneath 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. For the new order alert, put your own number into Your WhatsApp numbers first, comma separated if there are several.
  8. Place a test order and move it through the states you turned on. Read the shop's log in var/log for lines from the njiwa channel: each one names the order, the event, the number and Njiwa's own message id, or the reason nothing went.
  9. 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 plugin listens to two events and no others. CheckoutOrderPlacedEvent is dispatched once, by the one route that turns a cart into an order, which is why an address corrected an hour later messages nobody. StateMachineTransitionEvent is dispatched by the registry every transition goes through, whoever asked for it: an administrator, a payment handler finalising a return from the bank, the admin API, or a scheduled task. None of those routes is a hole. Which state machine a transition belongs to is read from the entity name, because cancelled exists on three of them and means three different things, and the states are matched by Shopware's own constants, so a shop that renamed a status in its own language still matches and a shop that added a status of its own does not.

The listener itself sends nothing. It checks the master switch, whether a key is saved and whether that event is on, then dispatches a small message carrying an order id and an event name onto Shopware's async transport. Anything that goes wrong in there is caught and written to the log, because an order must never fail to be placed, and a payment must never fail to be marked paid, over a WhatsApp message.

The worker reads the order again rather than trusting what was true when the job was queued, and checks every setting a second time, so a merchant who switches something off means it from that moment on, including for work already in the queue. It renders the wording, claims its row, and posts to /v1/messages with your key, an Idempotency-Key and a User-Agent naming the plugin and its version. The request has a 20 second ceiling on the whole call, not just on the gaps between bytes, so a server that trickles an answer for ever cannot hold a queue worker for ever.

Failures are sorted into two kinds. A network error, a 429 or anything in the 500s is Njiwa saying not now: the message was never accepted, so the claim is handed back and the queue tries again. A refusal, such as a bad key or a number that is not on WhatsApp, arrives with a stable machine readable code and a sentence a person can act on; both are written down and the message is left alone, because repeating it only fills the queue with work that will fail the same way.

Get it

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