# Keyword rules

Bird ships the keyword catalog, so a recipient who replies `STOP` to one of your inbound-capable numbers is opted out with no setup on your part. `START` reverses it. A rule of your own overrides Bird's default for the scope it covers.

This page covers what Bird recognizes, how an inbound message is matched, and how to change the wording or add keywords. The [**Keywords**](https://bird.com/dashboard/w/whatsapp/keyword-rules) page is the dashboard equivalent.

![A list of keyword rules, with the workspace rules above the default rules they override](/images/docs/dashboard-whatsapp-keyword-rules.png)

## What Bird recognizes by default

Nine words record an opt-out:

`stop`, `stop all`, `stopall`, `unsubscribe`, `cancel`, `end`, `quit`, `revoke`, `optout`

Two reverse it: `start` and `unstop`.

Matching is on the whole message rather than a substring. Because `cancel` and `end` are keywords, that distinction matters: "cancel my 3pm delivery" is an ordinary message rather than a withdrawal of consent. Letter case, accents, repeated spaces, and trailing punctuation are ignored, so `Stop!` and `STOP` both match. Punctuation before or inside the word is not ignored, so `#stop` does not match.

## Where keywords are not matched

A keyword inside a group message is skipped, so a participant cannot opt out by replying there. Honor a group member's stated opt-out in your own sending logic.

## When the preference is recorded

Classification runs alongside the `whatsapp.received` event rather than ahead of it. An integration watching that event can therefore see a `STOP` arrive before the preference it records exists. If your handler reacts to the inbound message by sending something, re-read the recipient's records rather than assuming the event order.

## Seeing what applies

`GET /v1/whatsapp/keyword-rules` describes how replies to your numbers are handled. Unfiltered, it returns Bird's catalog alongside any rules you have created. Narrow it with `country`, `waba`, `operation`, or `scope`:

- `scope=system` returns Bird's catalog, including the default that a rule of yours overrides.
- `scope=workspace` returns your own rules.

**TypeScript**

```typescript
const rules = await bird.whatsapp.keywordRules.list({ operation: "opt_out" });
for (const rule of rules.data ?? []) {
  console.log(rule.scope, rule.effective_keywords);
}
```

Examples: [TypeScript](/docs/guides/whatsapp/opt-outs/keyword-rules.ts.md) · [Python](/docs/guides/whatsapp/opt-outs/keyword-rules.py.md) · [Go](/docs/guides/whatsapp/opt-outs/keyword-rules.go.md) · [PHP](/docs/guides/whatsapp/opt-outs/keyword-rules.php.md) · [CLI](/docs/guides/whatsapp/opt-outs/keyword-rules.cli.md) · [MCP](/docs/guides/whatsapp/opt-outs/keyword-rules.mcp.md) · [cURL](/docs/guides/whatsapp/opt-outs/keyword-rules.curl.md)

Rules come back most specific first, which is the order an inbound message is matched against them. Each carries `effective_keywords`: Bird's set for that operation and country, plus anything you added.

For a rule of your own with no `country`, `effective_keywords` shows Bird's worldwide set, because the rule has no country and the sender's is unknown until a message arrives. That rule matches against Bird's set for the sender's country, which can be a larger set. Set a `country` on your rule to see exactly what those senders match.

`country` is the sender's country, worked out from their own phone number rather than the number they messaged. It is the country signal WhatsApp sends. A sender identified by a business-scoped user ID carries no country, so a message from one skips the country-scoped rules and matches a worldwide rule instead.

## Changing the reply

Bird's default replies are correct but generic. To answer in your own name, create a rule:

**TypeScript**

```typescript
const rule = await bird.whatsapp.keywordRules.create({
  operation: "opt_out",
  country: "US", // the SENDER's country, from their own number
  reply: "You're off the list. ACME Courier won't message you again.",
});
// effective_keywords is Bird's set plus any of your own.
console.log(rule.id, rule.effective_keywords);
```

Examples: [TypeScript](/docs/guides/whatsapp/opt-outs/keyword-rules.ts.md) · [Python](/docs/guides/whatsapp/opt-outs/keyword-rules.py.md) · [Go](/docs/guides/whatsapp/opt-outs/keyword-rules.go.md) · [PHP](/docs/guides/whatsapp/opt-outs/keyword-rules.php.md) · [CLI](/docs/guides/whatsapp/opt-outs/keyword-rules.cli.md) · [MCP](/docs/guides/whatsapp/opt-outs/keyword-rules.mcp.md) · [cURL](/docs/guides/whatsapp/opt-outs/keyword-rules.curl.md)

Your rule replaces Bird's reply for that scope and keeps Bird's keywords. Your `keywords` are additions rather than a replacement, so a keyword Bird ships later starts matching with no change from you.

To send nothing while still recording the opt-out, omit `reply` when you create the rule. To silence an existing rule, set `reply` to `null` in a JSON body on the update below; a CLI flag cannot carry `null`.

![A dialog for adding a keyword rule, with fields for extra keywords and a reply](/images/docs/dashboard-whatsapp-keyword-rule-dialog.png)

## Adding your own keywords

**TypeScript**

```typescript
// Omitting keywords leaves the set alone; an empty array clears your additions
// back to Bird's. reply: null switches the auto-reply off and still records
// the opt-out.
const rule = await bird.whatsapp.keywordRules.update("wkr_01m2kj8x4te9p0rr7e5w2n1abc", {
  keywords: ["no more texts", "remove me"],
});
console.log(rule.effective_keywords);
```

Examples: [TypeScript](/docs/guides/whatsapp/opt-outs/keyword-rules.ts.md) · [Python](/docs/guides/whatsapp/opt-outs/keyword-rules.py.md) · [Go](/docs/guides/whatsapp/opt-outs/keyword-rules.go.md) · [PHP](/docs/guides/whatsapp/opt-outs/keyword-rules.php.md) · [CLI](/docs/guides/whatsapp/opt-outs/keyword-rules.cli.md) · [MCP](/docs/guides/whatsapp/opt-outs/keyword-rules.mcp.md) · [cURL](/docs/guides/whatsapp/opt-outs/keyword-rules.curl.md)

Omitting `keywords` leaves your additions alone. Sending an empty array clears them back to Bird's set.

## Rule scope and duplicates

A rule narrows to one WhatsApp Business Account with `waba`, to one sender country with `country`, to both, or to neither. You hold one rule per combination of operation, country, and account. A second write for the same combination returns a duplicate error.

Bird rejects a rule that binds `stop` to `opt_in`, whether the word came from Bird's catalog or from another of your rules, so an opt-out word cannot be made to grant consent.

## Deleting a rule

Deleting your rule hands that scope to the next rule in the match order, which is not always one of yours. The order runs:

1. Your rule for an account and country.
2. Your rule for the account.
3. Your rule for the country.
4. Bird's rule for the sender's country.
5. Your worldwide rule.
6. Bird's worldwide rule.

Deleting your rule for a country therefore hands the scope to Bird's rule for that country before your own worldwide rule:

**TypeScript**

```typescript
// The next rule in the ladder answers the scope, which is another rule of yours if you hold a less specific one; STOP never stops working.
await bird.whatsapp.keywordRules.delete("wkr_01m2kj8x4te9p0rr7e5w2n1abc");
```

Examples: [TypeScript](/docs/guides/whatsapp/opt-outs/keyword-rules.ts.md) · [Python](/docs/guides/whatsapp/opt-outs/keyword-rules.py.md) · [Go](/docs/guides/whatsapp/opt-outs/keyword-rules.go.md) · [PHP](/docs/guides/whatsapp/opt-outs/keyword-rules.php.md) · [CLI](/docs/guides/whatsapp/opt-outs/keyword-rules.cli.md) · [MCP](/docs/guides/whatsapp/opt-outs/keyword-rules.mcp.md) · [cURL](/docs/guides/whatsapp/opt-outs/keyword-rules.curl.md)

Deleting a rule does not stop `STOP` working. It returns the wording and the keyword set to whichever rule comes next in that order.

## Next steps

- [Preferences](/docs/guides/whatsapp/opt-outs/preferences): the records a keyword creates, and which of them you can reverse.
- [Suppressions](/docs/guides/whatsapp/opt-outs/suppressions): the addresses your workspace blocks directly.
- [Opt-outs and keywords for SMS](/docs/guides/sms/opt-outs-and-keywords): the same mechanism on the other channel, which also carries help and campaign keywords.

## Related resources

- [Connecting WhatsApp to Bird: from buying a number to a live channel](/learn/whatsapp/connecting-whatsapp-to-bird) (video)
- [What is the 24-hour customer service window on WhatsApp?](/explained/whatsapp/what-is-the-24-hour-customer-service-window) (answer)
- [WhatsApp message builder](/tools/whatsapp-message-builder) (tool)
- [WhatsApp](/whatsapp-api) (product)

[Get an implementation brief](/learn/workspace?topic=whatsapp)
