A Card Cannot Be Empty
A Google Chat card is refused if it contains nothing. That sounds like it could never happen, and it happens on the shortest notification you will ever send — the one that is a headline and nothing else.
Every notification in this system is a required title and three optional pieces. That shape is fine on five chat platforms and produces an invalid document on the sixth, for a reason that is completely reasonable and entirely invisible until it happens: a Google Chat card is made of widgets, and a card with no widgets is not a minimal card. It is a malformed one.
Where the emptiness comes from
A card here has a header and a body. The header takes the title and, where there is one, a small subtitle naming the source. The body is a list of widgets, and the connector adds two of them conditionally.
-
A paragraph, if there is detail
The body of the notification. Plenty of alerts have one; plenty do not.
-
A button, if there is somewhere to go
A link back to the record. Present whenever the event is about one thing.
-
And that is the entire list
The title lives in the header, not in the body. So a notification that is only a title contributes nothing to the widgets at all.
That third point is the whole trap. The title is not missing — it is in the header, and the header is fine. The body is what is empty, and it is empty precisely when the message is at its simplest.
The failure case is the shortest, most ordinary message in the system, and it is the one nobody builds a test for.
The fallback, and why it reads oddly at first
So when the widget list comes out empty, the title is added to the body as a paragraph — meaning it appears twice: once as the header and once beneath it.
That looks like a bug for about five seconds. Then you consider the alternatives and it stops looking like one.
| Option | What the reader gets | Verdict |
|---|---|---|
| Send it anyway | Nothing. The card is refused. | The message does not arrive |
| Fall back to a plain-text message | A different-looking notification with no header | Two shapes in one channel, inconsistently |
| Add a placeholder widget | A card with a blank line in it | Reads as broken |
| Repeat the title | A slightly redundant card that arrives | Chosen |
The redundancy is real and it is the cheapest thing on the list. A card that says the same short sentence twice is mildly inelegant. A card that does not exist is a notification nobody received.
It is worth noticing what makes this decision easy: the failure is total rather than partial. If an empty body merely produced an ugly card, leaving it alone would be defensible. Because it produces no card, the bar for the workaround is very low.
A card identifier, which is not decoration
Each card carries an identifier. Every notification from this connector uses the same one.
It is required by the format, and the sensible thing is to use one stable value rather than generating something unique per message. A stable identifier says these are all the same kind of card from the same source, which is what it is for.
The alternative — a generated identifier per message — would make each notification look like a different kind of card to anything downstream, and would buy nothing, since nothing here needs to address a specific card afterwards.
One header that is not like the others
A small honest note about a difference between the chat connectors, because somebody comparing them will see it.
On Slack, Teams and Telegram, the link's label travels with the notification — an approval can say Review the request while a stock alert says something else. On Google Chat the button carries a fixed label.
In practice the difference is small: the card's heading is what tells the reader what this is, and the button is the way through rather than the explanation. It is mentioned because these posts are written off the code, and a claim of per-notification labelling would be true of three of the six and not this one.
And a header nobody else sends
One more small difference, in the request rather than the card. This connector states the character encoding on its content type; the other five send the type alone.
The reason is that Google's own documentation specifies it, and following a provider's stated contract rather than the shape that happens to work is the cheaper habit over time — the request that works because a default happened to match is a request that breaks when the default changes.
It also removes any ambiguity about non-Latin text, which in an operational context is not hypothetical: supplier names, place names and people's names carry accents, and a notification is a poor place to discover an encoding question.
What is in place, layer by layer
The Google Chat connector as it stands
A native card, not pasted text
A header carrying the title, a subtle subtitle naming the source, and a body of widgets — so notifications look like they belong in the space.
A guaranteed non-empty body
A notification with no detail and no link would produce a card the platform refuses, so the title is carried into the body rather than sending nothing.
Conditional widgets
A paragraph when there is detail and a button when there is a destination, rather than empty furniture where there is neither.
Every text field capped
Title, subtitle and body are each trimmed before sending, so a long record produces a shortened card rather than a refused one.
A stable card identifier
One value for every notification, so all of them read as the same kind of card from the same source rather than as unrelated documents.
The encoding stated explicitly
The content type names the character set as the provider documents, which removes any question about accented text in a supplier or place name.
A refusal before the call
An unconfigured destination returns a reason without making a request, so a half-set-up connector reads as unconfigured rather than as failing.
A bounded wait
A twelve-second ceiling on the request, so a slow response cannot hold up the work that raised the notification.
The provider's rejection surfaced
A refused card reports Google's own status and body, which names the offending part rather than reporting a generic failure.
The button's label is fixed on this platform rather than supplied per notification, which is a real difference from three of the other chat connectors and is noted rather than smoothed over.
Three positions held on purpose
- A slightly redundant message that arrives beats an elegant one that is refused. Where the failure is total rather than cosmetic, the bar for an inelegant workaround is very low and pretending otherwise is vanity.
- The provider's documented contract is followed rather than the shape that happens to work. A request that succeeds because a default matched is a request that breaks when the default moves, and nobody will connect the two events.
- Differences between the six connectors are stated rather than smoothed over. A claim that is true of three platforms and not the fourth is worse than no claim, because it will be believed exactly where it is wrong.
Five questions about a card-based notification
What happens to a title-only notification?
A good answer sounds like
It arrives.
What ours actually is
The title is carried into the card body as well, because a card with nothing in its body is refused.
What if a record has a very long name?
A good answer sounds like
Trimmed, not refused.
What ours actually is
Every text field is capped before sending, with the heading surviving intact.
Can the reader reach the record?
A good answer sounds like
A button.
What ours actually is
Present whenever there is one destination. Its label is fixed on this platform.
How is accented text handled?
A good answer sounds like
Explicitly.
What ours actually is
The encoding is stated on the request as the provider documents, rather than left to a default.
What do I see when a card is refused?
A good answer sounds like
Google's own reason.
What ours actually is
Their status and body, passed through, naming the offending part of the card.
Our take
Structured message formats are a real improvement over pasting text into a channel, and they come with a cost that only shows up at the edges. A plain string is valid whatever it contains, including nothing. A document has rules, and the rules are usually about the cases you did not think of — the empty list, the missing optional field, the message that turned out to be shorter than the format's minimum. Every one of those is trivially fixable and none of them is discoverable from the happy path, which is why the fix always arrives after somebody notices a category of notification quietly never turning up.
Alerts in the space your team already uses
If the working day happens in Google Chat, approvals and exceptions should arrive there as native cards rather than as pasted text.
Talk through operational alertsFrequently asked questions
Why does a short alert show the same line twice?
Because a Google Chat card with an empty body is refused outright rather than rendered plainly. When a notification has no detail and no link, the title is carried into the body as well so the card is valid. It is mildly redundant, and the alternative is a notification that never arrives.
Can we change what the button says?
On this platform the label is fixed. Three of the other chat connectors do carry a per-notification label; this one does not, and it is worth stating plainly rather than implying otherwise. The card's heading is what tells the reader what the message is about.
Do accented characters in supplier names come through correctly?
Yes. The request states its character encoding explicitly, as Google's documentation specifies, rather than relying on a default matching. That is the one place where an operational system meets this question routinely — names of people, places and companies.
Can we send to several Google Chat spaces?
A webhook is bound to one space by the platform, so a connection is a space. Routing different event types to different destinations is how most teams end up separating a busy alerts space from a quieter approvals one.
What does a rejected card look like from our side?
Google returns a status and a body naming what it did not like, and both are passed through unchanged. That is usually enough to identify the field, which is the point of not translating it into a house phrase.