Njiwa for Medusa

A Medusa 2 integration that sends a WhatsApp message when an order is placed, paid, packed, shipped, cancelled or refunded, and tells you when one comes in.

Start a 7 day trial See what your customer gets

What changes for your business

After the checkout your storefront goes quiet, and this does not

A Medusa shop hands the customer a confirmation page and then the order moves on without them. Paid, packed, handed to a courier: all of it happens in the admin, where you are the only person who can see it. So they ring, they email, they message the shop's social account, and somebody has to stop what they are doing and go and look. Every one of those steps now tells the customer itself, the moment it happens, on the app already open on their phone.

Packed and posted are two different days

Most shops pick the orders in the morning and hand the day's parcels over in the afternoon, and a customer told it has shipped when it has only been packed starts counting the delivery days from the wrong one. Medusa keeps those as two separate moments and so does this: one switch and one wording for the packing, another for the courier. Turn on the one that matches how you really work, or both on purpose, and the customer hears the true thing at the time it becomes true.

New orders reach your phone, not just the admin

One message to your own number the moment an order is real, carrying the customer's name, how many items, the total, how they paid and a link that opens that order in your Medusa admin from the phone in your hand. List several numbers and everybody on the list gets their own copy, so whoever packs finds out when you do rather than when you get round to telling them. It goes on the order being placed, once, so a checkout somebody walked away from never wakes you up.

A refund stops being an argument

Cancellations and refunds turn into phone calls, and they turn into phone calls because the customer finds out late and from somewhere that is not you. A cancelled order says plainly, and straight away, that they have not been charged. A refund says the money is on its way back and that a bank takes a few days to show it, which is the sentence that stops the second call. It names no amount, so it is still true when you have sent back part of an order rather than all of it.

Changing a message is not a deploy

On a shop built the Medusa way, the words a customer reads usually live in code, so a sentence that reads wrong is a ticket, a branch and a release before it is a fix. These are not. They sit on the Njiwa WhatsApp page in your Medusa admin, where whoever runs the shop can edit one, save it, and have the next order go out in the new wording: nothing to restart, nothing to deploy and nobody to wait for. Each one arrives written well enough to send unedited, so turning an event on is one click rather than a writing job.

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, we have your order #1043 for KES 18,400. We will let you know as soon as it is on its way. 2 x Tyre 195/65 R15 1 x Wheel balancing Riverside Hardware

09:24
The order is placed

Hi Mary, thank you. Your payment for order #1043 has gone through and we are getting it ready. Total KES 18,400 Riverside Hardware

11:06
Payment lands

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

13:41
Handed to a courier

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

15:18
The money goes back

New order #1043 on Riverside Hardware. Mary Wanjiku 3 item(s), KES 18,400 Paid by M-Pesa https://riverside.example/app/orders/order_01J8Z3XK5M7NQ2P4R6T8V0W2Y

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 Medusa sends, and to whom
When it happens Who hears about it
An order is placedThe customer: we have your order, with the items and the total. Medusa emits order.placed when a cart becomes an order and when a draft order is confirmed, which is the first moment an order is real. It is the message for bank transfer, cash on delivery and anything else placed before the money arrives.
The payment is capturedThe customer: the money has landed and you are getting the order ready. The one most shops want. A shop that captures in two parts emits this twice and the customer is still told once.
The order is packedThe customer: it has been packed and is leaving shortly. This is the moment somebody in the shop fulfils the items, which is not the moment it is handed to a courier.
The order is handed to a courierThe customer: it is on its way. A separate moment from packing, with its own switch and its own wording. Most shops want this one or the packed one, and a shop that turns on both is telling the customer twice on purpose.
The order is cancelledThe customer: cancelled, and you have not been charged. A cancellation nobody explained is what turns into a phone call.
A refund is recordedThe customer: the money is coming back, and banks take a few days to show it. The message names no amount, because a refund can be for part of an order, and only the first refund on an order sends one.
An order comes in, and you want to knowYou, on every number you listed in Your WhatsApp numbers: the order number, the customer, how many items, the total, how they paid, and a link that opens the order in your Medusa admin. It goes on the same order.placed moment, once per order, so an abandoned checkout never wakes you up.

Why it is safe on a shop that is trading

Every event is off until somebody turns it on. Installing this plugin cannot cause a message to be sent, and a shop that adds it and never opens the settings page sends nothing at all. Each event ships wording that works unedited, and clearing that box is how you turn one message off while leaving the event on.

The master switch, Send WhatsApp messages, keeps every setting and sends nothing when it is off. It fails loudly rather than silently: an order that would have been messaged writes a line saying the switch is off, and the two buttons on the settings page answer with the same reason instead of pretending to work.

Sending never blocks a checkout. Medusa 2 runs the subscribers after the request has been answered, and nothing in this plugin's path is allowed to throw into Medusa, so the worst that WhatsApp being slow can do is delay a WhatsApp message.

A customer is not told twice. The idempotency key covers a subscriber that runs twice within 24 hours, and the marker on the order covers everything after that, including a Medusa restart that replays its event queue.

It cannot post to a WhatsApp group. Anything with an @ in it is dropped whole rather than stripped down to its digits, wherever a number is accepted: in Your WhatsApp numbers, in the test message box, and in Send from, which refuses it outright. An address ending @g.us is a group, and one new order posted to a group would message hundreds of people from your own number.

A local number is completed from the country on the address it was typed against, so 0712345678 on an order shipping to Kenya becomes 254712345678. A number already written in full, with a plus or a leading 00, is believed and left alone, so a customer abroad does not get a second country code stuck on the front.

Fulfilling or shipping with "do not notify the customer" ticked sends nothing, because that is what it means, and the log says that is why.

A customer with no phone number is normal, not an error. Nothing is sent, nothing is complained about, and a line in the log says the order had no number on it, so a quiet order is explained rather than mysterious.

The two buttons are built to be safe to press. Test connection only asks which numbers your account has and sends nothing at all. The test message wording is fixed in the code, so the page cannot be turned into a way of sending whatever somebody types from your own number, and it is limited to five messages in five minutes. Both sit behind Medusa's own admin authentication, and the settings page never prints your API key back: it shows only whether there is one and whether it is a test key.

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

Will my customers be messaged as soon as I install it?

No. Every event is off until you turn it on, and nothing can be sent until there is a Njiwa API key in your config. A fresh install sends nothing.

How do I try it without messaging anybody?

Use a key beginning sk_test_. Every message is checked and stored and nothing reaches a phone, and the settings page, the Test connection button and the test message all say plainly that the key is a test key. Both buttons are ordinary admin routes, so you can also call them from a terminal with a Medusa admin API key if you prefer.

Does it work on Medusa 1?

No. Medusa 2 changed how plugins are built, loaded and configured, and nothing here works on 1.x. This is built against Medusa 2.20 and needs Node 20.19 or newer.

Why is the API key not on the settings page?

Because Medusa gives a plugin nowhere encrypted to keep a secret. Everything the page saves goes into your store's metadata, which is plain text that anyone with a dashboard login can read back, so a key saved there would be a key handed to all of them. It stays in your environment, and you rotate it by changing that variable and restarting. The Njiwa address stays with it for the same reason: it decides where the key is sent, so somebody who could repoint it from the admin could collect the key.

Where do I see what was sent?

On the order. Its metadata carries a njiwa entry per event with the time, the numbers, Njiwa's message id, or the refusal and its code, and the Medusa log carries the same line as it happens. There is no message list inside the Medusa admin; the Njiwa console is where every message, its status and its failure reason are kept.

What happens if I fulfil in two parts, or refund twice?

One message per event per order. The first fulfilment sends the packed message and a later one sends nothing, and the same is true of a second refund. That is also why the refund message names no amount: it would be wrong for a partial refund and there is no placeholder for one.

Can several of us get the new-order message?

Yes. Put the numbers in Your WhatsApp numbers, separated by commas, and everybody listed gets their own copy. Put {admin_url} in that wording and the message opens the order in your Medusa admin from your phone.

Has this been run against a real Medusa shop?

Not yet. It is built against Medusa 2.20, and the message renderer, the phone number parser and the settings checks have tests that run without a database, a queue or an event bus anywhere near them. No live Medusa 2 install has sent it a real order. Start with a test key, watch the Medusa log and the order metadata, and tell us what you find.

What it does not do

Said plainly, so you are not surprised later

  • It has not been run against a live Medusa 2 install. The code is complete and has tests behind the parts that can be tested without Medusa, and neither of those is the same thing as a real order from a real shop.
  • It does not receive replies. Incoming WhatsApp messages arrive as webhooks, and verifying one needs that number's signing secret, which the console does not show yet. Until it does, a receiving feature could not prove a request came from Njiwa, so there is not one.
  • It does not show a list of messages in the Medusa admin. What was sent is on each order's metadata and in the Medusa log, and the text itself is not kept: Njiwa already stores every message, its status and its failure reason.
  • It does not tell you a message was delivered. The marker records that Njiwa accepted the message and the id it gave, which is not the same as WhatsApp putting it in front of somebody. Delivery and read updates are in the Njiwa console.
  • It sends one message per event per order. A second fulfilment, a second capture and a second refund send nothing, and the refund message names no amount because there is no placeholder for the amount of one refund.
  • It sends text. Attaching the PDF invoice or an image to one of these messages is not a setting yet, though the Njiwa API itself does it.
  • It does not run campaigns. Sending to past customers in bulk is what the Njiwa console is for, on Business plans and above.
  • The API key cannot be changed from the admin. It is an environment variable and a restart, for the reason given above, and so is the Njiwa address.
  • It reads one set of settings for the whole install. There is one master switch, one set of wording and one sending number for every order, rather than one per sales channel or region, and Language tag changes how money and dates are written rather than translating the wording.
  • It runs on Medusa 2 only, and it is built against 2.20. An older 2.x may work and has not been tried.

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

  • Medusa 2. The plugin is built against Medusa 2.20 and asks for @medusajs/framework and @medusajs/medusa 2.20 or newer. Nothing here works on Medusa 1.x, which loaded and configured plugins in a different way.
  • Node 20.19 or newer on the 20 line, or Node 22.12 or newer.
  • Access to medusa-config.ts and to the environment your Medusa server starts with. The API key is set there rather than in the admin, and the reason is on this page.
  • A Njiwa account and an API key. The 7 day trial key works, and a key beginning sk_test_ is the one to start with.
  • Nothing else. The plugin adds no runtime dependency to your install: it uses Node's own fetch, it owns no tables, and there is no migration to run.

Install it

  1. On the machine that runs Medusa, install it from its repository. It is deliberately not on the public npm registry, where that name belongs to nobody and could one day be somebody else's package installed into your server. npm builds it on the way in, so there is no build step of your own.
    Terminal
    npm install github:Upeosoft-Limited/njiwa-medusa
  2. In the Njiwa console, under API keys, create a key beginning sk_test_. It checks and stores every message and delivers nothing to a phone.
  3. Put that key in the environment your Medusa server starts with, as NJIWA_API_KEY.
  4. Open medusa-config.ts and add the plugin to the plugins array, as { resolve: "njiwa-medusa", options: { apiKey: process.env.NJIWA_API_KEY } }. That is all the config needs.
  5. Leave modules alone. The plugin registers its own module, so there is nothing to add there, and there is no migration to run.
  6. Restart Medusa.
  7. Open the Medusa admin and click Njiwa WhatsApp in the sidebar. Everything else is set on that page.
  8. Check that the Connection section at the top of it says test key, and shows https://njiwa.upeo.ai as the address. If it says not set, Medusa did not see the environment variable.

Set it up

  1. Fill in Send from only if you have more than one linked number, in full international form with the country code and no leading zero. Empty uses the number marked default on your Njiwa account, which is the right answer for one number.
  2. If you want the new-order message, put your own numbers in Your WhatsApp numbers: digits with the country code, commas between them.
  3. Set Shop name, Language tag, your storefront's order page and where your admin can be reached, or leave them. Each one says on the page what it falls back to when it is empty.
  4. Press Save. Both buttons use the settings as they are saved, not as they are on screen, so save first and check second.
  5. Press Test connection. It lists the WhatsApp numbers your Njiwa account really has, with their state and which one is default, and says plainly when the key is a test key. It sends nothing.
  6. Type your own number next to Send test message and press it. That sends one fixed message with the key as saved and reports what Njiwa said, which is the part that proves the whole path.
  7. Turn on the events you want and edit the wording under each one. Every event is off until you turn it on, and each ships wording that works unedited.
  8. Place a test order and watch what happens: the Medusa log names every message and the id Njiwa gave it, and the order carries a njiwa entry in its metadata saying what went where.
  9. Only then change NJIWA_API_KEY to the sk_live_ key and restart Medusa. A live key sends real messages to real phones, and real messages cost money.

How it works

Six subscribers listen for the moments that are actually true: order.placed, payment.captured, order.fulfillment_created, shipment.created, order.canceled and payment.refunded. Nothing listens for a general "the order changed" event, which fires when somebody corrects an address and would message a customer for it. Two of those events do not name an order: a captured or refunded payment is followed to the payment collection and then to the order, and a fulfilment or shipment event is read for the order it carries, or looked up through the fulfilment when it carries only that.

From there every event takes one path. The settings are read from your store as they are now, not as they were when Medusa started. If the event is off, nothing happens. If it is on but there is no key, or the master switch is off, a line says so rather than leaving silence that looks like a working shop. The order is then read with only the fields a message can use, the marker for that event is checked, the recipient is worked out, and the wording is filled in. A placeholder that does not exist is taken out before sending and named in the log, so a typo never reaches a customer.

The message goes to POST /v1/messages over Node's own fetch, with a 20 second timeout so nothing holds a worker, and with an idempotency key that is the same for the same order, event and recipient. A refusal from Njiwa comes back with its own stable code, which is written on the order and into the log with the sentence Njiwa gave. Afterwards the marker is written, and only a message Njiwa accepted counts as sent.

The settings live in two layers. The options in medusa-config.ts are the boot layer, read once when Medusa starts; the Njiwa WhatsApp page in the admin saves overrides into your store's metadata, and those are laid over the top on every send. The API key and the Njiwa address stay in the boot layer on purpose: Medusa gives a plugin nowhere encrypted to keep a secret, the store metadata is plain text that any admin login can read back, and the address decides where the key gets sent.

Get it

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