Njiwa for Odoo

WhatsApp your customers when a sales order is confirmed, an invoice is paid or a delivery is validated, and get a message yourself when an order comes in.

Start a 7 day trial See what your customer gets

What changes for your business

Nobody rings to ask whether the money arrived

Three questions take up most of the phone: is my order in, did you get my money, has it gone out yet. Odoo knows all three the moment they become true, and this is what tells the customer, on the app already open on their phone. The order, the invoice and the delivery each keep a note of what was sent and to which number, so when somebody says nobody told them you can look rather than argue.

Nobody has to tell a customer the payment came through

Somebody still registers the payment or reconciles the statement, the way they always did. What goes away is the follow up. The moment money is matched against a customer invoice, that customer is thanked and told there is nothing more to pay, and nobody has to remember to write it. How the money got there does not matter: a payment registered on the invoice, a bank statement reconciled days later and a payment provider posting its own entry all reach the same message. A part payment sends nothing, because a customer who still owes you should not be thanked, and an invoice cleared by a credit note sends nothing either, because nobody paid.

It adds no work, and it slows nothing down

There is no new step in anybody's day and nothing to forget on a busy morning: the messages ride on the work already being done, which is confirming the quotation, validating the delivery, posting the credit note. Nor is anyone waiting on WhatsApp while they do it. Confirming an order writes one line to a queue and returns, and a scheduled action named Njiwa: send queued WhatsApp messages sends it a minute later, so a slow line or an unreachable Njiwa cannot hold up a confirmation, a delivery or a payment.

You hear about a sale without watching Odoo

When a quotation is confirmed, a message can go to your own number as well: who bought, how many items, the total, and a link that opens the order in Odoo from your phone. Everybody whose number you list gets their own copy, once per order. A quotation nobody has confirmed sends nothing, so the pipeline does not wake you up at eleven at night.

It reads as though your company wrote it

The total carries the currency the document is actually in, the date is written the customer's way, and where a contact has a language set the figures and the state of the order come out in it. Quantities read the way a person writes them, so two tyres are 2 rather than 2.0, and a long order prints ten lines and then says how many more there are, because nobody reads a WhatsApp message with two hundred lines in it. Every message ships written and works unedited; changing a sentence is editing a box on Odoo's own settings page, and clearing that box is how you switch one message off and keep the rest.

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 S01043 for KSh 18,400. We will let you know as it moves along. 2 x Tyre 195/65 R15 1 x Wheel balancing Riverside Hardware

09:24
You confirm the order

Hi Mary, thank you. Your payment for INV/2026/01043 has come through, in full, and there is nothing more to pay on it. Riverside Hardware

11:06
The invoice is paid

Hi Mary, your order is on its way. Our delivery reference is WH/OUT/01043. 2 x Tyre 195/65 R15 Riverside Hardware

13:41
The delivery goes out

Hi Mary, we have credited KSh 18,400 back to you on RINV/2026/01043. Where the money is going back to a bank account, it takes a few days to show. Riverside Hardware

15:18
You post a credit note

New order S01043 on Riverside Hardware. Mary Wanjiku 3 item(s), KSh 18,400 https://riverside.example/web#id=1043&model=sale.order&view_type=form

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 Odoo sends, and to whom
When it happens Who hears about it
A quotation is confirmed and becomes a sales orderThe customer, told you have their order, what it comes to and what is on it. Confirming is the moment an order is real: writing a quotation is not, and neither is editing an order later.
A customer invoice is fully paidThe customer, thanked and told there is nothing more to pay. It goes at the moment the payment is matched against the invoice, so a payment registered by hand, a bank statement reconciled in the accounts and a gateway posting its own entry all reach it.
A delivery order going out is validatedThe customer, told it is on its way, with the delivery reference and the quantities that actually went out. Deliveries out only: a receipt from a supplier and an internal transfer between your own locations message nobody.
A sales order is cancelledThe customer, told the order has been cancelled and invited to reply if that is not what they expected. It waits for the cancellation to really happen, so opening the confirmation wizard and closing it again tells nobody.
A credit note is postedThe customer, told the money is coming back. The wording that ships says it is on its way rather than that it has arrived, because a bank takes a few days and the difference is a message you would otherwise have to answer.
A quotation is confirmed, for you rather than the customerYou, on every number listed in Your WhatsApp numbers, one copy each, once per order. It carries the customer, the item count, the total and a link that opens the order in Odoo.

Why it is safe on a shop that is trading

Every event is off until you turn it on. Installing the addon and opening the settings page sends nothing to anybody. The master switch is off, all six events are off, and there is no key saved, so the first message leaves only after somebody has pasted a key and ticked boxes on purpose.

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, chatter notes and message ids included, so you can confirm a real order and read the whole trail before a customer is involved. The notes and the test buttons both say when a test key is in force.

It cannot stop you confirming, shipping or taking money. Every hook is wrapped in a try and a database savepoint, and the sending happens somewhere else entirely, a minute later, in a scheduled action. A failure to arrange a message is a line in the Odoo log, never an error thrown at the person confirming the order.

A customer is never messaged twice for the same thing. Every message carries a key made from the database, the document, the event and the recipient. The queue refuses to write a second row for a key it already holds, and the database enforces that as well as the addon. Njiwa honours the same key for 24 hours and replays the first answer rather than sending again, so a job that runs twice costs nobody a second message, and the queue row outlives those 24 hours, so a document that somehow reaches the same moment twice, months apart, still sends once. The recipient is part of the key, so a new order alert going to three of your own numbers stays three messages rather than collapsing into one, and the database name is hashed into it, so two Odoo databases sharing one Njiwa account never collide over S00042.

It cannot message a WhatsApp group. Everything typed into a number box, and everything read off a contact, is reduced to digits, between 7 and 15 of them, before it is used. A group address ends @g.us and is not digits, so it cannot survive the trip. One confirmed order can never become a message to hundreds of people from your own number.

No phone number is not an error. A customer without a number is normal. Nothing is sent, nothing is raised, the order confirms as usual, and a note on the document says who had no number.

Clearing a box is a decision, not a fault. An empty wording sends nothing, queues nothing and writes nothing on the document, whatever the tick box beside it says. That is how one message is turned off without turning the event off, and it is deliberately quiet, so orders do not fill up with red lines about a message you chose not to send.

Switched off means switched off, loudly. With the master switch off nothing is sent and nothing is thrown away: queued rows stay where they are and go out if you switch it back on within the day. Anything that tries to send while it is off gets a clear refusal that names the reason, so a mistake is findable rather than silent.

The key is kept where only an administrator can read it. It lives in Odoo's system parameters and the field is marked for the Settings administrator group, so a portal user, a customer with a login and an ordinary salesperson cannot see it. Uninstalling the addon deletes the key and the rest of its settings; the notes on your orders stay, because they are a record of what was sent.

The button that spends money is fenced. Send test message is open to Settings administrators only, limited to ten sends an hour across the whole database, and its wording is fixed in the code. You supply the recipient and nothing else, so it cannot be talked into saying something it should not.

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

Which versions of Odoo does it work with?

Odoo 16.0 and 17.0. It is one addon rather than two: the places where the versions differ are handled in the code, and Odoo puts its own series in front of the version number when it reads the manifest. It has not been written or checked against 18.0.

Do I need queue_job, a worker, or anything else installed first?

No. Sending is done by an ordinary Odoo scheduled action that runs every minute, which any database already has. What it does need is that scheduled actions are actually running on your database: if they are switched off you will see the queue under Sales, Configuration, Njiwa messages filling up and nothing leaving it.

Can a WhatsApp message stop an order being confirmed or a payment registered?

No, and that is the promise the addon is built around. Every hook sits inside a try and a database savepoint, and the message is not sent there at all: it is queued, and a scheduled action sends it a minute later. If Njiwa is down, or the key is wrong, or the wording will not render, the order still confirms and the problem is written to the Odoo log and to the queue.

Will it message everybody if I import or confirm a batch of old orders?

It can, so do the import with the master switch off. Nothing fires on creating a record or editing one, so loading old quotations messages nobody. But confirming a hundred orders at once, validating a hundred deliveries or reconciling a hundred payments is a hundred real moments, and the addon has no way of telling a catch-up from today's work. Switch Njiwa on again when the history is in.

Which number does it send to, and what if the customer has none?

The contact's Mobile first, then their Phone, then the same two on the company that contact belongs to, which is what lets a delivery address with no number of its own still reach somebody. The first usable number wins. Numbers are read as people write them: 0712345678, +254 712 345 678 and 254712345678 all work, and a box holding two numbers separated by a slash or a comma is split rather than run together into a number belonging to nobody. If there is no number at all, nothing is sent and the document says so.

I turned it on and nothing arrived. Where do I look?

Three places, in this order. The document: its chatter says what went where, or why nothing did. The queue at Sales, Configuration, Njiwa messages: rows sit there as Waiting until the scheduled action runs, and a failed row carries the reason Njiwa gave. Then the Njiwa console, which holds every message it accepted and what happened to it afterwards. A note saying it was sent on a test key means it was stored and deliberately not delivered.

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 addon 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.

Is it ready for my live database?

Here is the honest answer. The addon is complete, it ships tests covering the number parser, the wording, the way the settings are read back and the cleared message box, and it has been read line by line against both supported versions, but nobody has yet run it on a production Odoo database at a real business. Install it on a copy of your database first, run it on a test key, and confirm a few orders before you put the live key in.

What it does not do

Said plainly, so you are not surprised later

  • 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. The queue holds who was messaged, about which document, when, with what outcome and Njiwa's message id, and no message text. Njiwa already stores the messages themselves, and a second copy is a second thing to keep in step.
  • It sends plain text only. No PDF, no image and no location on any of the six events, and no attachment of the invoice Odoo has already generated. Put {order_url} in the wording instead: it links to the customer's own order or invoice and carries its own access token, so it opens without a login. Njiwa's API does send files, and a developer can post one from Odoo in a few lines.
  • There are six moments and no more. Nothing for a quotation being sent, an invoice being posted, a purchase order, a manufacturing order, a repair or a helpdesk ticket, and no way to add one without writing code.
  • There are no scheduled messages. No payment reminder before a due date, no statement at month end, no follow up on a quotation that has gone quiet. Everything is sent because something happened, not because a date arrived.
  • There is one wording per event for the whole database. The settings are system parameters, so a database running several companies shares one key, one sending number and one set of wording between them, and there is no wording per customer or per language. The values inside a message are rendered in the contact's own language where they have one; the sentences around them are the ones you typed.
  • There is no opt out box on a contact. A customer who asks to stop hearing from you has to have their number taken off the record, or the event switched off for everybody.
  • It does not run campaigns. Sending to a list of past customers is what the Njiwa console is for, on Business plans and above.
  • A message that has waited more than a day is not sent, and a message Njiwa refused is not retried. Both are written on the queue row and on the document, and putting either right is something a person does.
  • It is not on the Odoo Apps store. Installing and upgrading both mean copying the folder onto your addons path, restarting Odoo and updating the addon from the Apps list.
  • A local number is read against your own sending number's country, not against the partner's address, because an Odoo partner's country is often the company's own rather than the customer's. 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

  • Odoo 16.0 or Odoo 17.0. Every hook, field and view tag in the addon was written against both, and the two places where the versions differ, the uninstall hook and the field that holds a delivered quantity, are handled in the code rather than left to chance.
  • Sales, Invoicing and Inventory. The addon declares them, so Odoo installs any that are missing when you install it. It needs nothing else: no queue addon, no extra worker, and no Python package beyond the HTTP library Odoo already ships with.
  • Scheduled actions running on the database. The sending is done by a scheduled action that runs once a minute, so on a database where scheduled actions are switched off the queue fills up and nothing leaves it.
  • Somewhere to put the folder. The addon is installed by copying it onto a directory in your Odoo addons path and restarting Odoo, so it needs a database you can add addons to. There is no Apps store listing to install it from yet.
  • Outbound HTTPS from the Odoo server to njiwa.upeo.ai. Nothing has to reach Odoo from outside: the addon only makes calls out.
  • A Settings administrator account. The settings page, both test buttons and the message queue are open to that group and to nobody else, and the API key is not in the form at all for anybody below it.
  • A Njiwa account and an API key. A test key is enough to install the addon, set it up and rehearse the whole thing before a customer is involved.

Install it

  1. Clone it, then copy the njiwa folder from inside it into a directory that is on your Odoo addons path. It is the njiwa folder that moves, not the repository folder around it: the technical name of an Odoo addon is its folder name, and this one must stay njiwa.
    Terminal, 2 lines
    git clone https://github.com/Upeosoft-Limited/njiwa-odoo
    cp -r njiwa-odoo/njiwa /path/to/your/addons/njiwa
  2. Restart Odoo, so that the new addon is on the list it reads at start up.
  3. Turn on developer mode, in Settings, at the bottom of the General Settings page. Update Apps List is only offered to a database in developer mode.
  4. Open Apps and press Update Apps List, so Odoo reads the folder you have just copied in.
  5. Search Apps for Njiwa and install the addon called "Njiwa: WhatsApp for your customers". Odoo installs Sales, Invoicing and Inventory first if the database does not have them.
  6. Check that the scheduled action arrived. In developer mode, Settings, Technical, Scheduled Actions, there is one called "Njiwa: send queued WhatsApp messages", running every minute. That action is the whole of the addon's sending, so if it is not there or not active, nothing will go out later.

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 afterwards, so a lost key is replaced rather than recovered.
  2. In Odoo, go to Sales, then Configuration, then Njiwa (WhatsApp). That page is the only place these settings live, and only a Settings administrator can open it.
  3. Paste the key into API key, and tick "Send WhatsApp messages". Tick it before you press anything else: the master switch is checked on every call the addon makes, the two test buttons included, so with it off Test connection answers that Njiwa is switched off rather than telling you about your key. Leave Njiwa address exactly as it is unless you were given an address of your own.
  4. Press Test connection. Odoo saves the form before it runs the button, so what you have just typed is what gets checked. It lists the WhatsApp numbers your account really has with the state of each and which is the default, and it says plainly when the key is a test key. It asks a question and sends nothing.
  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. A leading zero is refused here on purpose, because there is no other number to read it against. Leave it empty to use the number marked default in the console. Test connection warns you when what you typed matches no number on the account, because every message would then be refused.
  6. Put your own WhatsApp number into the "Send a test message to" box and press Send test message. Write it however you have it: 254712345678, +254 712 345 678 and 0712345678 are all read correctly. One short fixed message goes out and the answer is the real outcome, with the message id. The wording is fixed in the code and the button is limited to ten sends an hour.
  7. Tick the events you want, further down the same page, and read the wording under each one. Every event ships with wording that works before anybody edits it, so turning one on is one click rather than one click and a writing exercise. Clearing a box is how you stop one message while leaving the event on.
  8. Fill in "Your WhatsApp numbers" if you want to hear about new orders yourself, separated by commas. Everybody listed gets their own copy. Keep {admin_url} in that message: it opens the order in Odoo straight from your phone.
  9. Press Save these settings. Odoo's own save on this page writes the Njiwa settings too, so either one keeps your work, and the button adds a plain answer that it worked.
  10. Rehearse it on the test key. Put your own mobile number on a test customer, confirm a quotation for them, and wait a minute for the scheduled action. The order's chatter then carries an internal note saying what went where with Njiwa's message id, and the same message is listed under Sales, Configuration, Njiwa messages.
  11. When the notes read the way you want, paste your sk_live_ key in place of the test key and save. From that moment the messages reach real phones and count against your plan.

How it works

The addon listens to five moments in Odoo and to nothing else: action_confirm and the cancellation path on a sales order, button_validate on a delivery, _post on a credit note, and the creation of the reconciliation that settles an invoice. There is deliberately no hook on write. An order is written to when somebody corrects a delivery address or changes the salesperson, and a customer messaged for that learns nothing and starts ignoring the ones that matter. Each moment is checked after the fact rather than assumed, so an order that opened a wizard and cancelled nothing, or a delivery whose backorder wizard is still on the screen, sends nothing until the thing has really happened.

Payment received is the one that cannot be hooked where you would expect it. An invoice's payment state is written by the ORM underneath a normal save, so the addon watches the moment underneath instead: money being matched against the invoice. Every way of paying ends there. It then sends only if the invoice is a customer invoice, posted, and now settled in full: a part payment sends nothing, because the customer still owes you and being thanked halfway is confusing, and an invoice cleared by a credit note sends nothing either, because nobody paid.

At any of those moments the addon checks the master switch, then that particular event, then finds a number for the customer, and writes one queue row inside the transaction that is confirming the document. All of it sits inside a try and a database savepoint, so a message that cannot be arranged is written to the Odoo log and the confirmation carries on. Because the row is written in that same transaction, a confirmation rolled back further down the line takes the queued message with it, and the customer hears nothing about an order that does not exist.

A minute later the scheduled action picks up what is waiting, up to a hundred rows at a time, and sends each one on its own with its own idempotency key and a 20 second timeout. The wording is read again at that moment rather than trusted from queueing time, so a box you cleared in the meantime sends nothing. Each message is committed by itself, so one refusal cannot roll back the four that went before it. A network failure is not a send failure and is offered again for a few minutes; a refusal from Njiwa is not retried, because it has read the message and said no, and the reason is written on the queue row and on the document. Anything still waiting after a day is dropped with that written on it, since a customer told on Thursday that their Monday order is confirmed only wonders what else is wrong.

Get it

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