“Waiting for this message” is a decryption failure, not a delivery failure
WhatsApp's grey placeholder means the message arrived and the key did not. What causes it, why the group version is permanent, and the variant that reports itself to your API as delivered.
Someone sends a message. On the other end it arrives as a grey placeholder: "Waiting for this message. This may take a while." Search for it and you get the same four suggestions everywhere — check your connection, restart your phone, make sure WhatsApp is up to date, wait a bit.
That advice is aimed at a problem which is not happening. The message is not in transit. It is not queued on a server somewhere waiting for a better signal. It already arrived, in full, and it is sitting on the device right now. What is missing is the key to open it.
That distinction is the whole story, and it matters enormously if you are running WhatsApp through an API rather than tapping a phone screen — because almost none of the standard fixes are things a gateway can do, and one of the failure modes reports itself to you as a successful delivery.
The bytes arrived, the key didn't
WhatsApp encrypts every message end to end with a key that exists for exactly one message. The two devices in a conversation each keep a running cryptographic state — a ratchet — that advances by one step with every message, deriving the next key and discarding the last. Neither side stores a master key that opens everything, which is the point: compromise one message and you have not compromised the conversation.
The cost of that design is that both sides must stay in step. The receiving device can only derive the key for a message if its session state lines up with the state the sender encrypted against. When those diverge, the ciphertext on the receiving device is not slow, or partial, or queued. It is unopenable, with the same finality as a locked box whose key was thrown away.
WhatsApp chooses to render that as a placeholder that looks like a loading state. It reads as "this will finish soon", when what actually happened is closer to "this arrived and I cannot read it". Waiting does not conjure the missing key. Something specific has to happen instead, and we will get to it.
Two different failures wearing the same placeholder
The placeholder is one string covering two problems with very different severities.
In a one-to-one chat, it is usually a stale pairwise session. The other person reinstalled WhatsApp, moved to a new phone, restored from a backup, or had their keys rotate underneath them. Their device now encrypts against an identity your device has never agreed a session with. Everything sent from that new state is undecryptable by the old one. This is the common case, and it is usually recoverable.
In a group, it is usually a missing sender key, and that is worse. Groups do not encrypt separately for every member on every message — that would not scale past a handful of participants. Instead the sender generates a sender key, distributes it once to each member's device over the pairwise channel, and from then on encrypts each group message a single time against that key.
If your device misses that distribution message — it was offline at the wrong moment, it was linked to the account after the fact, the pairwise session was churning — then it cannot decrypt anything that person subsequently sends to that group. Not one message: all of them, indefinitely. And there is no mechanism in the client to say "I missed your sender key, please send it again."
This is why the symptom "one specific person's messages in one specific group are permanently placeholders, but their DMs are fine" is so recognisable and so frustrating. It is not the same bug as the DM case. It is persistent by construction, and no amount of restarting fixes it.
The recovery mechanism nobody mentions, and the trap it sets
There is a real repair path in the protocol, and it explains why waiting sometimes works: the retry receipt.
When a device receives something it cannot decrypt, it can tell the sender so. The sender then re-encrypts that message against a freshly negotiated session and sends it again. If it works, the placeholder quietly resolves into text a few seconds or minutes later and nobody thinks about it again.
Here is the part that matters if you operate a gateway, and that consumer troubleshooting guides have no reason to mention: you are on the receiving end of retry receipts too. Every message your platform sends is a message some device may later come back and ask you to encrypt again.
To answer that, you must still have the plaintext of a message you already sent. The library WALayer is built on, whatsmeow, exposes exactly this surface — a hook to look up the original message for a retry, a callback to decide whether to honour one, and, critically, a setting for whether recently sent messages are persisted in the database or held only in an in-memory cache.
That last one is a trap with a very specific shape. If your sent-message store lives only in memory, then every deploy, crash, pod eviction and scale-down silently destroys your ability to answer retries for everything sent before it. A recipient whose device asks for a retry across that boundary gets nothing back, and their placeholder becomes permanent.
Nothing in your telemetry will tell you. You sent the message. You got an acknowledgement. Your dashboard is green. On a platform that deploys a few times a week, this converts a routine, self-healing failure into a permanent one for some unlucky slice of recipients, over and over, invisibly.
The silent one: delivered, but to nobody who can read it
The worst variant does not look like a failure at all.
WhatsApp is in the middle of migrating how accounts are addressed, from phone numbers to an internal identifier called a LID. Signal sessions established against the phone-number identity do not always migrate cleanly to the LID identity. When that migration fails, there is no session with the recipient's primary device — but their companion devices, which negotiate fresh sessions through prekeys, receive the message fine.
Those companion devices then send delivery receipts. So:
- The sender sees two grey ticks. Delivered.
- Your delivery webhook fires. Your logs record a success.
- The recipient's actual phone shows nothing, or a placeholder.
- The underlying failure is logged at debug level and does not stop the send.
This is documented in whatsmeow as issue #1027, and it is the one that should worry anyone building on delivery acknowledgements. A delivery receipt is evidence that a device accepted the ciphertext. It has never been evidence that a human can read it, and under this bug those two things come apart completely.
If your order confirmations, appointment reminders or one-time codes treat "delivered" as "the customer has it", this is the case where that assumption quietly fails — and it fails without producing a single error anywhere in your system.
What you can actually do about it
Being honest about the split: some of this is yours to fix, and some of it genuinely is not.
Things worth doing on your side:
Persist sent messages durably enough to answer retry receipts across restarts. If you deploy often, an in-memory-only cache means you are regularly and permanently breaking messages you successfully sent.
Stop treating a delivery ack as proof of readability. For anything that matters — a code, a confirmation, a payment link — the trustworthy signals are a read receipt or, better, a reply. Build the fallback path (resend, SMS, email) on the absence of those, not on the absence of a delivery ack that will happily arrive for a message nobody can open.
Handle undecryptable inbound messages instead of dropping them. whatsmeow raises a distinct event when a message arrives that cannot be decrypted, including a flag for whether a ciphertext was even sent to this device. If your inbound pipeline ignores that event, undecryptable messages do not appear anywhere: not in your message log, not in a webhook, not in an error. You have a hole in your history and no signal that it exists. Surfacing it as a real event — even one your customer can only act on by asking the sender to send again — is better than silence.
Do not manufacture the problem yourself. Which brings us to the one cause that is entirely within your control.
Things you cannot fix:
A missing sender key in a group. There is no protocol affordance to request it; the practical workaround is human, not technical — have the sender post again, or have the affected member leave and rejoin so a fresh distribution happens.
The other party reinstalling WhatsApp. Their session churn is theirs. Retry receipts are the mechanism, and yours is to be able to answer them.
The version of this you inflict on yourself
Every cause above originates on someone else's device. There is one that originates on yours, and it is the most preventable failure in this entire category: two processes holding the same session's state.
The ratchet assumes exactly one writer. Run two workers against one linked session — a deploy that overlaps, a scaled-out consumer, a supervisor that reattaches a session that was never actually released — and both advance the ratchet independently. Neither crashes. Nothing logs an error. The state simply diverges, and from that point every message you send is encrypted against a state your recipients cannot follow.
You have then generated "Waiting for this message" for your own customers' customers, at scale, from a bug entirely inside your infrastructure. We have written about why that happens and the ownership model that prevents it — it is the same root cause as the disconnection problems that look like network flakiness, wearing a different symptom.
It is also the reason WALayer treats single ownership of a session as an invariant rather than a best effort: one process owns a session's cryptographic state, enforced with an advisory lock, a renewable lease and a monotonic fencing epoch, and a runner that cannot renew its lease disconnects immediately rather than risk becoming the second writer. A disconnected session recovers on its own. A corrupted one takes a QR scan and a phone call to the customer.
The short version
"Waiting for this message" is a decryption failure dressed as a loading state. In a DM it usually resolves through a retry receipt. In a group it is often permanent, because a missed sender key cannot be re-requested. And in its nastiest form it hides behind a delivery acknowledgement that says everything worked.
If you run WhatsApp behind an API, the three things worth taking away: keep sent messages durable enough to answer retries, never mistake a delivery ack for a message someone can read, and make absolutely sure you are not the second writer on your own session.