Njiwa for Shopify

A small web service you install on your Shopify shop, which sends a WhatsApp message when an order is placed, paid, fulfilled, cancelled or refunded.

Start a 7 day trial See what your customer gets

What changes for your business

Shopify already knows; now the customer does too

Shopify records the moment a payment lands, an order goes out, a cancellation is entered and a refund is made. Your customer sees none of it, which is why they write and ask. Each of those four moments becomes a WhatsApp message as Shopify records it. Shopify's own email still goes out; this one arrives on the app already open on their phone. Nobody on your side is typing those replies any more.

You hear about a sale without watching the admin

The moment an order is real, a message reaches every number you listed: the customer, how many items, the total, how they paid, and a link that opens that order in your Shopify admin from your phone. On Shopify an order exists only once a checkout has completed, so an abandoned cart never wakes anybody up, and each real order tells you exactly once.

Waiting for the money is not the same as silence

Bank deposit, cash on delivery and anything else paid after the order is placed leaves a gap between the sale and the money, and that gap is where the phone calls come from. An order that arrives still waiting gets a message saying you have it and will say when the payment comes through, then a second one when it does. An order paid by card at the checkout skips the first and hears once, so nobody is asked about money they have already sent.

A refund does not become a second conversation

When you send money back, the customer is told the amount that is coming and that banks take a few days to show it, which is the sentence that stops the follow up message. Every refund gets its own, so refunding one item out of a larger order tells them what that item was worth instead of leaving them to work it out from the order total.

You can say what happened to a message

The settings page ends with the last fifty messages: which order, which moment, the last four digits of the number, and what became of it. A message Njiwa refused carries the reason there in plain words, and an order that had no usable number is listed as exactly that, so a customer who says they heard nothing gets an answer in seconds rather than a shrug.

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 KES 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 fulfilled

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

13:41
Cash on delivery order

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

15:18
You refund part of an order

New order #1043 on Riverside Hardware. Mary Wanjiku 3 item(s), KES 18,400 Paid by M-Pesa https://riverside-hardware.myshopify.com/admin/orders/5102847361094

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 Shopify sends, and to whom
When it happens Who hears about it
An order comes in still waiting for paymentThe customer: we have your order, and we will say when the payment arrives. Only for bank deposit, cash on delivery and other manual methods. A card paid at the checkout skips this and gets the payment message a moment later instead.
The order is paidThe customer: the payment came through and you are getting it ready, with the items and the total.
The whole order is marked fulfilledThe customer: it is on its way. Shopify sends its own email at the same moment; this arrives where people actually look.
The order is cancelledThe customer: cancelled, and the money is on its way back if they had paid.
A refund is madeThe customer: the amount of that refund is coming back. Sent for every refund, partial ones included, so two partial refunds are two messages.
An order comes inYou, once per order, on every number you listed: the order number, the customer, how many items, the total, how they paid, and a link that opens the order in your Shopify admin.

Why it is safe on a shop that is trading

Every event is off until somebody turns it on. Installing this app cannot cause a message to be sent, and no key is saved until you paste one. 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 to the log saying the switch is off, so a merchant wondering why nothing arrives finds an answer instead of silence.

Sending never blocks a checkout. Shopify is answered before Njiwa is called, so a slow or unreachable Njiwa can delay a WhatsApp message and can never lose you a sale.

A message goes once, or not at all. Every webhook id Shopify delivers is written down, so a redelivery is recognised before anything is decided. A row is claimed per shop, order, event and recipient before a message is sent, and a second attempt at the same one stops there. And each message carries an Idempotency-Key that Njiwa honours for 24 hours, so a send that runs twice replays the first answer instead of messaging the customer again.

It cannot post to a WhatsApp group. A value with an @ in it is refused whole rather than stripped down to its digits, because that is how a group is addressed and one new order could otherwise message hundreds of people from the shop's own number. The box for your own numbers takes digits with commas between them, and a piece that is not a phone number is named back to you when you save rather than quietly dropped.

An order Shopify marks as a test sends nothing. The Bogus gateway and a payment provider's test mode both put a real phone number on an order nobody placed, and a live key would message that person and charge you for it. Each one skipped writes a line to the log, and Message test orders too turns them back on while you are watching what comes out.

A customer with no phone number is normal. Nothing is sent, no error is raised, and the Recent messages table says the order had no usable number, so a quiet order is explained rather than mysterious.

Deleting the app from a shop clears the Shopify token and every setting including the Njiwa key, straight away. About 48 hours later Shopify sends shop/redact and the rest goes with it. The message text is dropped the moment Njiwa has it, and only the last four digits of a recipient are kept.

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}
  • {refund_total}

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

Can I install this from the Shopify App Store?

No. It is not in the App Store, and Shopify does not run other people's code inside a shop. Somebody has to host this small web service at a public https address, and the shop is then installed on it from that address. One running copy serves as many shops as you like: each keeps its own Njiwa key, its own sending number, its own wording and its own record of what has been sent, and nothing is shared between them, so an agency or a merchant with several stores hosts it once. If that sounds like somebody else's job, it probably is: hand the README to whoever looks after your servers.

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 you have saved a Njiwa API key. A fresh install sends nothing at all.

How do I try it without messaging anybody?

Save a key beginning sk_test_. Every message is checked and stored and nothing reaches a phone, and both the Test connection button and the message log tell you plainly that the key is a test key. Test connection sends nothing at any time, and orders Shopify marked as tests are skipped by default.

What happens if Njiwa is slow, or the server restarts mid-send?

Shopify is answered before Njiwa is called, so nothing waits on it. A send that never reached Njiwa is tried three times. A message written down but not yet sent when the process stopped is sent when it starts again, and the idempotency key means Njiwa replays its first answer rather than sending a second message.

Does it message abandoned carts?

No. On Shopify an order exists only once a checkout has completed, so an abandoned cart never reaches this app and never wakes you up. The new-order message to you is sent once per order, at the moment the order is real.

What does it send for a partial refund?

One message per refund. Two partial refunds on the same order are two real events and send two messages, and {refund_total} is the amount of that refund rather than the order total.

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 the wording and the message opens the order in your Shopify admin from your phone.

Has this been run against a real shop?

Not yet. The tests pass: they cover the webhook signatures, which moment sends which message, the once-only rules, the recovery after a restart, and the way numbers are read. But no live Shopify shop has sent it a real order, and a passing test is not that. Start with a test key, watch the message log, 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 Shopify shop. The code is complete and it has a test suite behind it, 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 run inside the Shopify admin. It opens as its own page from Apps, rather than embedded in the admin frame.
  • It does not keep a copy of your messages. Recent messages is the last fifty outcomes, and the text is dropped as soon as Njiwa has it. Njiwa already stores every message, its status and its failure reason.
  • It does not run campaigns. Sending to past customers in bulk is what the Njiwa console is for, on Business plans and above.
  • It does not send anything on a partial fulfilment. The message goes when the whole order is marked fulfilled.
  • It cannot message about a refund on an order more than sixty days old, because Shopify will not hand an app holding read_orders an order that old. Nothing is sent and the log says so.
  • It sends text. Attaching a PDF invoice or an image to one of these messages is not a setting yet, though the Njiwa API itself does it.

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

  • A Shopify shop, and a Shopify Partner account at partners.shopify.com to create the app in. Shopify does not run other people's code inside a shop, so this is a web service the shop talks to rather than a file you upload.
  • A server with a public https address that Shopify can reach. Shopify will not deliver a webhook to an http address, and it will not deliver one to a laptop.
  • Python 3.11 or newer.
  • Storage that survives a restart: an SQLite file by default, or Postgres. It holds each shop's Shopify token, each shop's settings and the record of what has already been sent.
  • 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.
  • One permission from the shop, read_orders, and nothing else. It is the first thing a merchant reads on the install screen.

Install it

  1. At partners.shopify.com, open Apps, then Create app, then Create app manually, and give it a name.
  2. Set App URL to the public https address the service will answer on, for example https://njiwa.example.com.
  3. Set the allowed redirection URL to that same address with /auth/callback on the end.
  4. Turn off "Embed app in Shopify admin". This app opens as its own page.
  5. Under App setup, compliance webhooks, put your address with /webhooks/shopify on the end in all three boxes: customer data request, customer redact, and shop redact. Shopify checks those three before it will let an app be distributed.
  6. Copy the Client ID and the Client secret from that page. The service will not start without them.
  7. On your server, clone it and install it from inside that directory, in a virtual environment of its own.
    Terminal, 3 lines
    git clone https://github.com/Upeosoft-Limited/njiwa-shopify
    cd njiwa-shopify
    pip install -e .
  8. Set SHOPIFY_API_KEY to the Client ID, SHOPIFY_API_SECRET to the Client secret, and NJIWA_SHOPIFY_APP_URL to the public address. The address has to match the Partner Dashboard to the character, because the redirect and every webhook address are built from it.
  9. Point NJIWA_SHOPIFY_DATABASE_URL at a file or a Postgres database that a redeploy will not start empty. That record is what stops a customer being messaged twice.
  10. Start it behind whatever terminates TLS for you.
    Terminal
    uvicorn njiwa_shopify.main:create_app --factory --host 0.0.0.0 --port 8000
  11. Check that GET /health answers with the version, and point your monitoring at it.
  12. Open https://your-app-address/?shop=example.myshopify.com and approve the permission screen. That stores the shop's token, reads the shop name and currency, registers the webhooks, and drops you on the settings page.
  13. Afterwards, get back in through Apps in the Shopify admin, which opens the same settings page already signed in.

Set it up

  1. 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.
  2. Paste it into API key on the settings page. Leave Njiwa address exactly as it is, and leave Send from empty unless you have more than one linked number; empty uses the number marked default on your account.
  3. Press Save. Both buttons below use the settings as they are saved, not as they are on screen, so save first and check second.
  4. Press Test connection. It lists the WhatsApp numbers your Njiwa account really has, says which is the default, and says plainly when the key is a test key. It sends nothing.
  5. Type your own number into Send me a 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.
  6. 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.
  7. If you want the new-order message, tick Tell me about new orders and put your own WhatsApp numbers in the box, digits only, commas between them.
  8. Place a real order, or tick Message test orders too while you watch, then read the Recent messages table at the bottom of the page.
  9. Only then paste the sk_live_ key and save. A live key sends real messages to real phones, and real messages cost money.

How it works

Installing the app on a shop registers six webhooks on it, all pointing at one address on your service: orders/create, orders/paid, orders/fulfilled, orders/cancelled, refunds/create and app/uninstalled. There is no orders/updated on purpose: it fires when somebody corrects an address, and a customer would be messaged for it. Every arriving webhook is checked against the app's client secret with HMAC SHA-256 before its body is read as anything, and one whose signature does not match is refused with a 401.

What happens next is decided without touching the network. The app works out who hears about this moment, renders the wording, and writes one row per message it means to send. Then it answers Shopify, and only then does it call Njiwa. Shopify gives an endpoint five seconds and sends the same webhook again if it does not get a 200, so the answer goes back before anything as slow as a network call is attempted. Each message carries an idempotency key built from the shop, the order, the event and the recipient, so the same message cannot be sent twice by two paths at once.

A refund webhook names its order and says nothing about the customer, so the order is fetched from Shopify's Admin API first, after Shopify has been answered. Shopify will not hand an app holding read_orders an order more than sixty days old; when that happens no message is sent and the log says why rather than leaving you wondering.

A row is written before the send starts and marked after it ends, so a process that stops in between leaves the message queued, and it is picked up and sent when the service starts again. A send that never reached Njiwa is tried three times with a widening pause, because the message was never accepted and trying again is safe. A refusal from Njiwa is not retried: it said no, and it would say no again, so the reason is recorded against that message and that is the end of it.

Get it

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