AWRA OpsHub Search

Two Hundred and Two Is Not a Receipt

Six chat providers report success six different ways — an empty body, the digit one, no content at all, a JSON document that says false while the status says fine. Deciding what counts as delivered is a real decision, and getting it wrong produces an integration that reports perfect health while nothing arrives.

Integrations & Data AWRA OpsHub Team 12 min read

There is a comfortable assumption underneath most integration code: the request came back without an error, therefore the thing happened. It survives contact with two or three providers and then it meets a fourth that returns a perfectly healthy response containing a polite refusal, and from that day the question stops being rhetorical. What exactly did this status code promise me?

Four codes, four different promises

Across the six chat destinations here, a successful send comes back in four distinct shapes.

Provider What success looks like What it actually promises
Slack A short body confirming receipt The message was accepted for that channel
Teams An empty body, or the single digit one The request was accepted — possibly for later processing
Discord No content whatsoever The webhook was executed
Google Chat A representation of the posted message The message exists in the space
Telegram A JSON document with its own success flag Read the flag — the status alone does not tell you
WhatsApp A JSON document with message identifiers Meta accepted it for delivery, which is not delivery

The Teams row is the one worth dwelling on, because it is the honest case rather than the awkward one. A workflow-backed webhook can answer accepted rather than done: the request is valid and queued, and the message will be posted shortly by machinery you are not talking to.

That is a completely reasonable thing for it to say, and it means the response is a receipt for the handover rather than for the delivery.

Accepted is a promise about the request. Delivered is a promise about the message. Almost every chat API gives you the first and is quoted as though it gave the second.

The provider that lies with a straight face

Telegram is the interesting one, and it is why the six connectors do not share one success test.

The Bot API can return a completely healthy status code carrying a document whose own success flag is false. The reason is inside: the chat identifier is unknown, the bot was removed from the group, the bot is blocked. From the transport's point of view nothing went wrong — a request was made, a response came back, everything is fine.

A connector that tested only the status code would count every one of those as sent. The counter would rise. Nothing would arrive. And the failure has the specific quality that makes this category dangerous: it is most likely for the destination that was removed, which is exactly the destination nobody is watching.

So the Telegram connector reads the body as well, and requires both the status and the flag inside it. Where it fails, the error it reports is Telegram's own description — chat not found, bot was kicked from the group chat — which is usually the whole diagnosis in four words.

The others are tested on the status alone, because on those providers the status is the answer. That is not inconsistency; it is the connector matching each provider's actual contract instead of imposing one on all six.

Three outcomes, not two

Underneath all six there is a shared structure worth naming, because it is the thing that makes a failure diagnosable.

  1. Reached and accepted

    The request arrived and the provider took it. The status is recorded, and for the provider that reports per-destination results, so is the count.

  2. Reached and refused

    The provider answered and declined. The status and the response body are both carried back as given — a revoked webhook, a malformed card and a rate limit are three different problems and read as three.

  3. Never reached

    The request did not complete: DNS, a timeout, a connection refused. The transport error is carried back instead, so the reason names the network rather than blaming the payload.

Collapsing the second and third into a single failure is the standard shortcut, and it costs whoever debugs it an afternoon. Could not reach Teams and Teams rejected the message send you to opposite ends of the problem. One is your network or theirs; the other is your configuration or your payload.

Merging the middle column into the right one is what turns a five-minute fix into an afternoon.
Merging the middle column into the right one is what turns a five-minute fix into an afternoon.

Why the provider's own words are passed through

Every one of these connectors surfaces the provider's response body verbatim on a refusal, rather than translating it into house language.

That is a deliberate choice against a tempting alternative. A tidy message — Could not send notification — reads better and is worth nothing. The provider's message names the field, the limit or the permission, and it matches what is in their documentation, which is where whoever is fixing it will end up.

It also ages better. A provider that adds a new refusal reason next year will say so in their own body, and it will appear, without anybody having to have anticipated it. A translation layer would report the new condition as whichever old category it most resembled.

The one thing deliberately kept out of that passthrough is the destination address, which is a credential on four of the six. The reason is shown; the thing that authorises posting is not.

And a ceiling on waiting

Every send is bounded — ten seconds on one provider, twelve on the rest. That number is not about speed, it is about what a notification is allowed to cost.

A chat message is a side effect of real work. Somebody approved a purchase order, a stock level crossed a threshold, an RFQ closed. If the chat provider is having a difficult afternoon, the correct behaviour is to give up and record why — not to hold the operation that raised the alert.

An unbounded wait on a notification is how one provider's outage turns into your outage, and the tell is that everything gets slow rather than anything getting an error.

What is in place, layer by layer

How a send is judged

Per-provider success tests

Five providers are judged on the response status; the one that can report failure inside a healthy response is judged on both the status and the flag in its body.

Built in

Three distinguishable outcomes

Accepted, refused, and never reached are separate results carrying different information, rather than one boolean.

Built in

The provider's reason, verbatim

A refusal carries the provider's own status and body, so the message matches their documentation instead of a house paraphrase.

Built in

Structured error extraction where offered

Providers that put a human-readable description in a known place have it read from there, so the reported reason is the sentence rather than the whole document.

Built in

Credentials kept out of the reason

The destination address never appears in an error or a log line, so a screenshot of a failure is not a leak of the thing that authorises posting.

Built in

A bounded wait on every send

Ten or twelve seconds depending on the provider, so a slow chat service cannot hold up the operation that raised the notification.

Built in

Per-destination counts where relevant

The two connectors that fan out to a list report how many succeeded and how many did not, rather than one verdict for the batch.

Built in

A refusal before the request

An incomplete configuration returns a reason without a network call, so an unconfigured connector is distinguishable from a broken one.

Built in

What none of these can tell you is whether a person read the message. That is not a limitation of the connector — no chat API reports it, and an integration that implied otherwise would be inventing the number.

Three positions held on purpose

  • Each provider is judged by its own contract rather than by a shared rule. One success test across six APIs is right on most of them and silently wrong on the one that reports failure inside a healthy response.
  • A failure to reach and a refusal on arrival stay separate. They point at opposite halves of the problem, and merging them into one message is the difference between a five-minute fix and an afternoon.
  • Accepted is reported as accepted, not as delivered. Where a provider promises only to have taken the request, that is what is recorded, because a stronger word would be a claim we cannot support.

Five questions about how a send is judged

What counts as success?

A good answer sounds like

A per-provider answer.

What ours actually is

The response status on five, and the status plus the flag in the body on the one that can refuse inside a healthy response.

Does it distinguish unreachable from refused?

A good answer sounds like

Yes.

What ours actually is

Three outcomes: accepted, refused with the provider's reason, and a transport error naming the network.

What do I see on a refusal?

A good answer sounds like

The provider's own words.

What ours actually is

Their status and body, passed through, so it matches their documentation.

How long will it wait?

A good answer sounds like

A fixed ceiling.

What ours actually is

Ten or twelve seconds. A slow provider does not become a slow workspace.

Can you tell me it was read?

A good answer sounds like

No, honestly.

What ours actually is

No chat API reports that. What is reported is acceptance, and it is called acceptance.

Our take

The most expensive bugs in integration work are not the ones that fail. They are the ones that succeed incorrectly — a counter that rises, a status that stays green, a channel that is silent, and no line anywhere connecting the three. What prevents them is not cleverness but a small, boring discipline: for each provider, read what their success actually claims, and write the check to that rather than to the shape of the last one you did. It takes twenty minutes per provider and it is the difference between a notification system people trust and one they eventually stop looking at.

Ask what green means

Any integration can show you a tick. The useful question is which of accepted, delivered and read that tick is actually reporting — and whether the product knows the difference.

Talk through operational alerts

Frequently asked questions

If a Teams send reports success, is the message definitely in the channel?

It means Microsoft accepted the request, and where a workflow-backed webhook answers with an accepted status, the posting itself happens moments later in their infrastructure. In practice the two are separated by a beat rather than by an outcome, but the honest description is acceptance, which is what gets recorded.

Why does Telegram sometimes report a failure when nothing looks wrong?

Because the Bot API can return a healthy status alongside a refusal — most often the bot has been removed from a group, or a chat identifier no longer exists. The reason Telegram gives is passed through, and it is usually the whole diagnosis in a few words.

What happens to a notification that fails?

The reason is recorded and the work that raised it continues. Chat is a signal rather than a system of record, so a failed alert never blocks an approval, a receipt or a transfer — the record in your workspace is the authority either way.

Can we get an alert when notifications stop arriving?

The most reliable check is the connection test on the settings screen, which sends a real message through the same path a notification takes. If it lands in the channel, the credential, the payload and the destination are all currently good.

Why is one provider's timeout shorter than the others?

It reflects how each provider actually behaves rather than a house standard. The purpose is the same across all six: a notification is a side effect of real work, and the ceiling is there so a slow chat service cannot become a slow workspace.

Help Center

Need a quick answer while you read?

Run inventory, procurement, assets, sales, and field work with approved AWRA guidance for setup, migration, integrations, security, pricing, and support.

Search all approved AWRA public help articles.

Open Help Center