# Message interactions

## SMS message interactions

SMS interactions are surfaced using the same **Message Interactions** API that other channels use. SMS supports a richer set of interactions than many channels because of compliance keyword handling, link-based opt-out/opt-in flows, and shortlink click tracking.

## List message interactions

> List message interactions

```json
{"openapi":"3.0.3","info":{"title":"Channels","version":"v1"},"servers":[{"url":"https://api.bird.com","description":"Production API"}],"security":[{"accessKey":[]}],"components":{"securitySchemes":{"accessKey":{"description":"Uses the Authorization header: 'AccessKey ' followed by your access key token","scheme":"AccessKey","type":"http"}},"schemas":{"MessageInteractionsList":{"type":"object","title":"ChannelMessageInteractionsList","description":"A list of channel messages interactions","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/channels.messages.interactions.interaction"}}}},"channels.messages.interactions.interaction":{"properties":{"bodyTemplateProjectId":{"format":"uuid","type":"string"},"channelId":{"format":"uuid","type":"string"},"context":{"$ref":"#/components/schemas/channels.messages.context"},"createdAt":{"format":"date-time","type":"string"},"details":{"type":"string"},"id":{"format":"uuid","type":"string"},"isFirstInteraction":{"type":"boolean"},"isFirstInteractionIncludingBots":{"type":"boolean"},"journey":{"$ref":"#/components/schemas/channels.messages.metadata.flow"},"messageCreatedAt":{"format":"date-time","type":"string"},"messageId":{"format":"uuid","type":"string"},"messagePartsCount":{"format":"int32","type":"integer"},"messageReference":{"type":"string"},"messageTags":{"items":{"type":"string"},"type":"array"},"metadata":{"$ref":"#/components/schemas/channels.messages.interactions.metadata"},"notification":{"$ref":"#/components/schemas/channels.notifications.message_notification"},"platformId":{"type":"string"},"platformReferenceId":{"type":"string"},"receiver":{"$ref":"#/components/schemas/channels.participants.receiver"},"type":{"$ref":"#/components/schemas/channels.messages.interactions.type"},"useCaseType":{"$ref":"#/components/schemas/channels.use_case_type"}},"required":["id","type","createdAt","messageId","channelId","platformId","messageReference","messagePartsCount","receiver","context"],"type":"object"},"channels.messages.context":{"properties":{"id":{"type":"string"},"tagIds":{"items":{"format":"uuid","type":"string"},"type":"array"},"type":{"type":"string"}},"type":"object"},"channels.messages.metadata.flow":{"properties":{"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"runId":{"format":"uuid","type":"string"},"scheduleExecutionId":{"format":"uuid","type":"string"},"stepId":{"type":"string"}},"title":"Message Flow Meta","type":"object"},"channels.messages.interactions.metadata":{"properties":{"button":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.button"},"conversion":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.conversion"},"extraInformation":{"additionalProperties":{"type":"string"},"type":"object"},"link":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.link"},"prefetched":{"type":"boolean"},"reaction":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.reaction"},"renderedContentBlocks":{"items":{"type":"string"},"type":"array"},"subscriptionList":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.subscription_list"}},"type":"object"},"channels.messages.interactions.metadata.button":{"properties":{"payload":{"type":"string"}},"required":["payload"],"type":"object"},"channels.messages.interactions.metadata.conversion":{"properties":{"method":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.conversion_method"},"status":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.conversion_status"},"timestamp":{"description":"Timestamp of when the conversion was recorded","format":"date-time","type":"string"},"type":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.conversion_type"}},"title":"Channel Message Conversion","type":"object"},"channels.messages.interactions.metadata.conversion_method":{"description":"Method of entering the code","enum":["unknown","manual","auto"],"title":"Message Interaction Conversion Method","type":"string"},"channels.messages.interactions.metadata.conversion_status":{"description":"Status of the conversion","enum":["converted","canceled","not_converted","incorrect_code","resent","received_after_expiration"],"title":"Message Interaction Conversion Status","type":"string"},"channels.messages.interactions.metadata.conversion_type":{"description":"Type of conversion","enum":["otp","url","promo_code"],"title":"Message Interaction Conversion Type","type":"string"},"channels.messages.interactions.metadata.link":{"properties":{"clickType":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.link_click_type"},"name":{"type":"string"},"url":{"type":"string"}},"required":["name","url"],"type":"object"},"channels.messages.interactions.metadata.link_click_type":{"enum":["link","button"],"title":"Message Interaction Link Click Type","type":"string"},"channels.messages.interactions.metadata.reaction":{"properties":{"action":{"$ref":"#/components/schemas/channels.messages.interactions.metadata.reaction_action"},"emoji":{"type":"string"}},"required":["emoji","action"],"type":"object"},"channels.messages.interactions.metadata.reaction_action":{"enum":["react","unreact"],"title":"Message Interaction Reaction Action","type":"string"},"channels.messages.interactions.metadata.subscription_list":{"properties":{"subscriptionListId":{"format":"uuid","type":"string"}},"required":["subscriptionListId"],"type":"object"},"channels.notifications.message_notification":{"properties":{"template":{"type":"string"},"url":{"type":"string"}},"required":["url"],"type":"object"},"channels.participants.receiver":{"properties":{"connector":{"$ref":"#/components/schemas/channels.participants.connector"},"contacts":{"items":{"$ref":"#/components/schemas/channels.participants.contact"},"type":"array"},"device":{"$ref":"#/components/schemas/channels.participants.device"},"inbox":{"$ref":"#/components/schemas/channels.participants.inbox"},"userContacts":{"items":{"$ref":"#/components/schemas/channels.participants.user_contact"},"type":"array"}},"type":"object"},"channels.participants.connector":{"properties":{"annotations":{"$ref":"#/components/schemas/channels.participants.contact_annotation"},"id":{"format":"uuid","type":"string"},"identifierValue":{"description":"The identifier value of the sender or receiver connector (e.g. email address or phone number).","type":"string"},"types":{"items":{"type":"string"},"type":"array"}},"required":["id"],"type":"object"},"channels.participants.contact_annotation":{"description":"Annotations add extra information to a sender or receiver. For email messages, the name annotation overrides the display name.","properties":{"name":{"type":"string"}},"type":"object"},"channels.participants.contact":{"properties":{"contactAnnotation":{"$ref":"#/components/schemas/channels.participants.contact_annotation"},"countryCode":{"type":"string"},"id":{"description":"The ID of the contact.","format":"uuid","type":"string"},"identifierKey":{"description":"The identifier key for finding the contact (e.g. emailaddress, phonenumber). For WhatsApp channels with BSUID support, use `whatsappbsuid` to send to a contact by their Business-Scoped User ID.","type":"string"},"identifierValue":{"description":"The identifier value for finding the contact.","type":"string"},"identifiers":{"description":"Additional identifiers to augment the contact during resolution.","items":{"$ref":"#/components/schemas/channels.participants.identifier"},"type":"array"},"platformAddress":{"description":"The resolved platform address used to deliver the message (e.g. phone number or email address).","type":"string"},"platformAddressSelector":{"description":"An expression that defines how we resolve the platform address from a contact. Optional override for the default resolution logic.","type":"string"},"receiverExpression":{"type":"string"},"receiverValue":{"type":"string"},"type":{"description":"The type of the receiver (e.g. to, cc, bcc for email).","type":"string"}},"type":"object"},"channels.participants.identifier":{"properties":{"identifierKey":{"description":"The identifier key for finding the contact (e.g. emailaddress, phonenumber).","type":"string"},"identifierValue":{"description":"The identifier value for finding the contact.","type":"string"}},"required":["identifierKey","identifierValue"],"type":"object"},"channels.participants.device":{"properties":{"gateway":{"$ref":"#/components/schemas/channels.participants.push_notification_gateway"},"id":{"format":"uuid","type":"string"},"identifierValue":{"type":"string"}},"required":["id","identifierValue","gateway"],"type":"object"},"channels.participants.push_notification_gateway":{"enum":["firebase","apns","web"],"type":"string"},"channels.participants.inbox":{"properties":{"emailRecipientType":{"type":"string"},"id":{"format":"uuid","type":"string"},"identifierKey":{"type":"string"},"identifierValue":{"type":"string"},"type":{"$ref":"#/components/schemas/channels.inbox_type"}},"required":["id","type","identifierKey","identifierValue"],"type":"object"},"channels.inbox_type":{"enum":["user","group"],"type":"string"},"channels.participants.user_contact":{"properties":{"id":{"format":"uuid","type":"string"},"identifierKey":{"type":"string"},"identifierValue":{"type":"string"},"type":{"type":"string"}},"required":["id","identifierKey","identifierValue"],"type":"object"},"channels.messages.interactions.type":{"enum":["invalid","read","opened","clicked","reported-as-spam","unsubscribe-request","delete","reaction","subscribe-request","subscribe-consent","conversion"],"title":"Message Interaction Type","type":"string"},"channels.use_case_type":{"enum":["unspecified","marketing","otp","transactional","conversation","test"],"type":"string"},"error.detailed":{"additionalProperties":false,"description":"An error returned from the API that includes additional details about the error. The `details` property can contain any additional information about the error that may be helpful for debugging or understanding the error.\n","properties":{"code":{"description":"A unique code that identifies the error. This code can be used to programmatically identify the error.","minLength":3,"type":"string"},"details":{"additionalProperties":true,"description":"Any additional information about the error that may be helpful for debugging or understanding the error.","type":"object"},"message":{"description":"A human-readable message that describes the error.","minLength":1,"type":"string"}},"required":["code","message"],"title":"DetailedError","type":"object"}},"responses":{"requestError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.detailed"}}},"description":"An error"}}},"paths":{"/workspaces/{workspaceId}/channels/{channelId}/messages/{messageId}/interactions":{"get":{"summary":"List message interactions","operationId":"listChannelMessageInteractions","description":"List message interactions","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageInteractionsList"}}}},"404":{"$ref":"#/components/responses/requestError"}}}}}}
```

#### Endpoint

* **GET** `/workspaces/{workspaceId}/channels/{channelId}/messages/{messageId}/interactions`

#### Response

* `ChannelMessageInteractionsList`:
  * `results` -- array of `ChannelMessageInteraction` objects.

Each interaction contains:

* `id` -- UUID of the interaction
* `messageId` -- UUID of the related message
* `channelId` -- UUID of the channel
* `platformId` -- `"sms-messagebird"`
* `type` -- one of the interaction types listed below
* `receiver` -- contacts array with `identifierKey` (`"phonenumber"`) and `identifierValue`
* `details` -- additional context string (see per-type sections below)
* `metadata` -- platform-specific details (link URLs, prefetch flags, etc.)
* `createdAt` -- timestamp of the interaction

***

### Supported interaction types

<table data-full-width="true"><thead><tr><th>Interaction</th><th>Description</th></tr></thead><tbody><tr><td><code>clicked</code></td><td>Recipient clicked a tracked shortlink in the message</td></tr><tr><td><code>unsubscribe-request</code></td><td>Recipient opted out via a keyword (STOP, etc.) or by clicking an opt-out link</td></tr><tr><td><code>subscribe-consent</code></td><td>Recipient opted in via a keyword (START, etc.) or by clicking an opt-in link</td></tr></tbody></table>

***

### Clicked interactions (shortlink tracking)

When shortlink tracking is enabled, URLs in the message body are replaced with tracked shortlinks. When a recipient taps one of these links, a `clicked` interaction is recorded against the original message.

Shortlink tracking is **not automatic** -- it must be explicitly enabled. There are two ways to enable it:

1. **On the message directly**: Include the `shortLinks` object in the message send request with a `domain` value (use `"default"` for the workspace's default shortlink domain).
2. **On a channel template**: Configure shortlinks at the template level. When sending via a template with shortlinks enabled, the template-level setting applies.

Messages sent without either of these will not have URLs shortened and will not generate `clicked` interactions.

**Enabling shortlinks on a direct API message**

```json
{
  "receiver": { ... },
  "body": { ... },
  "shortLinks": {
    "domain": "default"
  }
}
```

#### Example: shortlink click from a text URL

```json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "messageId": "d4f1d9f5-c21c-4c06-8cb6-2ec5a2e0a111",
  "channelId": "b443faa0-896c-5852-aca5-f87cffec68be",
  "platformId": "sms-messagebird",
  "type": "clicked",
  "receiver": {
    "contacts": [
      {
        "identifierKey": "phonenumber",
        "identifierValue": "+15551234567"
      }
    ]
  },
  "metadata": {
    "link": {
      "url": "https://example.com/promo",
      "clickType": "link"
    },
    "prefetched": false
  },
  "createdAt": "2026-02-19T14:30:00Z"
}
```

#### Metadata fields

<table data-full-width="true"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>metadata.link.url</code></td><td>string</td><td>The original destination URL the shortlink redirects to</td></tr><tr><td><code>metadata.link.clickType</code></td><td>string</td><td>Either <code>"link"</code> (inline text URL) or <code>"button"</code> (button-triggered URL)</td></tr><tr><td><code>metadata.prefetched</code></td><td>boolean</td><td><code>true</code> if the click was detected as a bot/crawler prefetch rather than a real user tap. SMS-specific bot user agents are filtered in addition to standard bot detection.</td></tr></tbody></table>

#### Bot and prefetch detection

Not all shortlink clicks are genuine user interactions. Link preview crawlers, carrier proxies, and security scanners can trigger automatic requests. Bird detects these and flags them with `metadata.prefetched: true`. Known SMS-specific prefetch user agents include Facebook/Twitter bots and Google PageRenderer.

***

### Unsubscribe interactions

The `unsubscribe-request` interaction is generated when a recipient opts out of receiving messages. This can happen in two ways:

#### 1. Keyword-based unsubscribe

When a recipient sends one of the recognized opt-out keywords as an inbound SMS, an `unsubscribe-request` interaction is recorded. The recognized opt-out keywords are:

<table data-full-width="true"><thead><tr><th>Keyword</th><th>Language</th></tr></thead><tbody><tr><td>STOP, STOPALL, END, QUIT, CANCEL, UNSUBSCRIBE</td><td>English</td></tr><tr><td>ARRET</td><td>French</td></tr><tr><td>HALT</td><td>German</td></tr><tr><td>DETENER</td><td>Spanish</td></tr><tr><td>PARAR</td><td>Portuguese</td></tr><tr><td>FERMA</td><td>Italian</td></tr></tbody></table>

Keywords are matched case-insensitively. Also see [Compliance Keywords Messages](/api/channels-api/api-reference/compliance-keywords-messages.md)

**Message association**

When a keyword-based unsubscribe occurs, the interaction is recorded against the **most recent marketing message** sent to that contact (i.e., a message where `meta.extraInformation.useCase` was set to `"marketing"`). If no recent marketing message is found, the interaction is recorded against the inbound STOP message itself.

This means the `messageId` in the interaction may refer to a previously sent outbound message, not the inbound STOP message.

**Example: keyword-based unsubscribe**

```json
{
  "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "messageId": "e5f6a7b8-c9d0-1234-5678-9abcdef01234",
  "channelId": "b443faa0-896c-5852-aca5-f87cffec68be",
  "platformId": "sms-messagebird",
  "type": "unsubscribe-request",
  "details": "keyword_unsubscribe",
  "receiver": {
    "contacts": [
      {
        "identifierKey": "phonenumber",
        "identifierValue": "+15551234567"
      }
    ]
  },
  "createdAt": "2026-02-19T15:00:00Z"
}
```

#### 2. Link-based unsubscribe

When you include the `{{optOutLink}}` system variable in your SMS message body, Bird generates a signed opt-out URL. When the recipient clicks this link and confirms, an `unsubscribe-request` interaction is recorded against the message that contained the link.

**Example: link-based unsubscribe**

```json
{
  "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
  "messageId": "d4f1d9f5-c21c-4c06-8cb6-2ec5a2e0a111",
  "channelId": "b443faa0-896c-5852-aca5-f87cffec68be",
  "platformId": "sms-messagebird",
  "type": "unsubscribe-request",
  "details": "link_unsubscribe",
  "receiver": {
    "contacts": [
      {
        "identifierKey": "phonenumber",
        "identifierValue": "+15551234567"
      }
    ]
  },
  "createdAt": "2026-02-19T15:05:00Z"
}
```

#### How to distinguish keyword vs link unsubscribes

Use the `details` field:

<table data-full-width="true"><thead><tr><th>Details Value</th><th>Trigger</th></tr></thead><tbody><tr><td><code>keyword_unsubscribe</code></td><td>Recipient sent an opt-out keyword (STOP, etc.)</td></tr><tr><td><code>link_unsubscribe</code></td><td>Recipient clicked the opt-out link (<code>{{optOutLink}}</code>)</td></tr></tbody></table>

***

### Subscribe-consent interactions

The `subscribe-consent` interaction is generated when a recipient opts back in. Like unsubscribes, this can happen via keywords or links.

#### 1. Keyword-based opt-in

When a recipient sends one of the recognized opt-in keywords, a `subscribe-consent` interaction is recorded. The recognized opt-in keywords are:

<table data-full-width="true"><thead><tr><th>Keyword</th><th>Language</th></tr></thead><tbody><tr><td>START, BEGIN, RESUME, REVERT, UNSTOP</td><td>English</td></tr><tr><td>NONARRET</td><td>French</td></tr></tbody></table>

Also see [Compliance Keywords Messages](/api/channels-api/api-reference/compliance-keywords-messages.md)

**Example: keyword-based opt-in**

```json
{
  "id": "d4e5f6a7-b8c9-0123-def0-234567890123",
  "messageId": "e5f6a7b8-c9d0-1234-5678-9abcdef01234",
  "channelId": "b443faa0-896c-5852-aca5-f87cffec68be",
  "platformId": "sms-messagebird",
  "type": "subscribe-consent",
  "details": "keyword_consent",
  "receiver": {
    "contacts": [
      {
        "identifierKey": "phonenumber",
        "identifierValue": "+15551234567"
      }
    ]
  },
  "createdAt": "2026-02-19T15:10:00Z"
}
```

#### 2. Link-based opt-in

When you include the `{{optInLink}}` system variable in your message, Bird generates a signed opt-in URL. When the recipient clicks this link and confirms, a `subscribe-consent` interaction is recorded.

**Example: link-based opt-in**

```json
{
  "id": "e5f6a7b8-c9d0-1234-def0-345678901234",
  "messageId": "d4f1d9f5-c21c-4c06-8cb6-2ec5a2e0a111",
  "channelId": "b443faa0-896c-5852-aca5-f87cffec68be",
  "platformId": "sms-messagebird",
  "type": "subscribe-consent",
  "details": "link_consent",
  "receiver": {
    "contacts": [
      {
        "identifierKey": "phonenumber",
        "identifierValue": "+15551234567"
      }
    ]
  },
  "createdAt": "2026-02-19T15:15:00Z"
}
```

#### How to distinguish keyword vs link opt-ins

Use the `details` field:

<table data-full-width="true"><thead><tr><th>Details value</th><th>Trigger</th></tr></thead><tbody><tr><td><code>keyword_consent</code></td><td>Recipient sent an opt-in keyword (START, etc.)</td></tr><tr><td><code>link_consent</code></td><td>Recipient clicked the opt-in link (<code>{{optInLink}}</code>)</td></tr></tbody></table>

***

### Monitoring interactions via webhooks

In addition to polling the interactions API, you can subscribe to real-time webhook notifications for SMS interactions.

#### Webhook event type

<table data-full-width="true"><thead><tr><th>Event type</th><th>Filter keys</th><th>Description</th></tr></thead><tbody><tr><td><code>sms.interaction</code></td><td><code>channelId</code>, <code>interactionType</code></td><td>Fires for any SMS message interaction</td></tr></tbody></table>

You can filter by `interactionType` to receive only specific interaction types. For example, subscribing with `interactionType: unsubscribe-request` will notify you of all opt-outs (both keyword and link-based).

***

### Marketing messages and use case classification

Interaction behavior -- particularly unsubscribe association -- depends on how the original message was classified. When sending a message via the API, set `meta.extraInformation.useCase` to `"marketing"` to classify it as a marketing message:

```json
{
  "receiver": { ... },
  "body": { ... },
  "meta": {
    "extraInformation": {
      "useCase": "marketing"
    }
  }
}
```

This classification controls:

1. **Unsubscribe association**: Keyword-based unsubscribes are linked to the most recent marketing message for that contact. Without classification, the interaction is recorded against the inbound STOP message itself.
2. **Frequency capping**: Marketing classification enables the `capFrequency` feature.
3. **Enforced opt-out links**: For SMS, Bird automatically injects an opt-out link into marketing messages if one is not already present.

***

### System variables for opt-out/opt-in links

Use these system variables in your message body to embed managed consent links:

<table data-full-width="true"><thead><tr><th>Variable</th><th>Description</th></tr></thead><tbody><tr><td><code>{{optOutLink}}</code></td><td>Generates a signed opt-out URL. For SMS, the <code>http://</code>/<code>https://</code> prefix is stripped to save character space.</td></tr><tr><td><code>{{optInLink}}</code></td><td>Generates a signed opt-in URL. Also stripped of protocol prefix for SMS.</td></tr><tr><td><code>{{unsubscribeUrl}}</code></td><td>Alias for the opt-out URL (includes protocol prefix).</td></tr></tbody></table>

These links are unique per message and recipient. They contain a signed token that authenticates the opt-out/opt-in action without requiring the recipient to log in.

***

### Interaction summary by trigger

<table data-full-width="true"><thead><tr><th>Trigger</th><th>Interaction type</th><th>details</th><th>messageId points to</th></tr></thead><tbody><tr><td>Recipient sends STOP/END/QUIT/etc.</td><td><code>unsubscribe-request</code></td><td><code>keyword_unsubscribe</code></td><td>Most recent marketing message (or the STOP message itself)</td></tr><tr><td>Recipient clicks <code>{{optOutLink}}</code></td><td><code>unsubscribe-request</code></td><td><code>link_unsubscribe</code></td><td>The message containing the opt-out link</td></tr><tr><td>Recipient sends START/BEGIN/etc.</td><td><code>subscribe-consent</code></td><td><code>keyword_consent</code></td><td>Most recent marketing message (or the START message itself)</td></tr><tr><td>Recipient clicks <code>{{optInLink}}</code></td><td><code>subscribe-consent</code></td><td><code>link_consent</code></td><td>The message containing the opt-in link</td></tr><tr><td>Recipient clicks a tracked shortlink</td><td><code>clicked</code></td><td>--</td><td>The message containing the link</td></tr></tbody></table>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.bird.com/api/channels-api/supported-channels/programmable-sms/message-interactions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
