# What does slack.message.posted prove?

> Prove that Slack holds this message in the intended conversation. Version 1.0.0, certification Provisional.

Canonical: https://provely.sh/verify/slack/slack.message.posted  
Last reviewed: 2026-09-05  
Skill version: 0.1.0  
Certification: provisional  

**Prove that Slack holds this message in the intended conversation. The conversation holds one message with the intended text at the ts that Slack returned. It does not prove that a person read the message.**

*Contract identity.*

| Fact | Value |
| --- | --- |
| Contract id | `slack.message.posted` |
| Version | `1.0.0` |
| Hash | `1165caca3a53de52` |
| Completion level | `posted` |
| Publisher | Built and signed by Provely. |
| Certification | Provisional |
| Skill | [Slack Web API 0.1.0](/verify/slack) |
| Valid for provider API versions | `fp_2026_09` |

## What is the intent?

Post the message into the conversation.

## What is the subject and the action?

| Member | Value |
| --- | --- |
| Subject type | `slack.message` |
| Subject identity | message_ts = `$action.result.ts` |
| Canonical effect | `messaging.message_post` |
| Provider operation | `POST /chat.postMessage` |
| Idempotency | none, retry is not safe |

## How does the evidence correlate with this operation?

| Strategy | Assurance | Keys | Required |
| --- | --- | --- | --- |
| `resource_id` | strong | message_ts from `$action.result.ts` | yes |
| `fingerprint` | weak | channel from `$input.channel_id`; text from `$input.text` | no |

## Which evidence does the contract require?

Minimum evidence level `E2`. An independent channel is required. Minimum channels: 1.

| Channel | Level | Independence | Verifier | Description |
| --- | --- | --- | --- | --- |
| `message_readback` | E2 | provider readback | `http` | Read the one message of the conversation that carries this ts. The documented recipe sets oldest to the ts, inclusive to true, and limit to 1. A condition path is $observed.message_readback.messages[0].ts. The path $observed.message_readback.ok states whether Slack answered. |
| `message_posted_event` | E3 | provider event | `webhook` | The message.channels events of this message in the intended conversation. The webhook verifier returns {events, count, latest, earliest, types, duplicates_dropped}. The filter on channel is the only read that proves the conversation: the answer of conversations.history names none. |
| `message_conversation_posts` | E3 | provider event | `webhook` | The messages of this text in this conversation since the operation started. The webhook verifier returns {events, count, latest, earliest, types, duplicates_dropped}. A count above one shows a second post, and chat.postMessage has no idempotency key to stop it. |

## Which conditions must all hold for VERIFIED?

| Condition | Meaning | Path | Operator | Expected | Evidence |
| --- | --- | --- | --- | --- | --- |
| `read_succeeded` | Slack answered the read. Every Web API response carries the boolean member ok, and Slack answers HTTP 200 for a failure as well, so the status code states nothing. | `$observed.message_readback.ok` | `eq` | `true` | `message_readback` |
| `message_present` | The conversation holds exactly one message at this ts. A hidden message, such as a deleted one, leaves the list. | `$observed.message_readback.messages` | `array_length` | `1` | `message_readback` |
| `message_ts_matches` | The ts of the message equals the ts of the answer of chat.postMessage. The member is a string in both places. | `$observed.message_readback.messages[0].ts` | `eq` | `$action.result.ts` | `message_readback` |
| `message_text_matches` | The text of the message equals the text that the intent stated. | `$observed.message_readback.messages[0].text` | `eq` | `$input.text` | `message_readback` |
| `message_posted_by_an_app` | The message carries the subtype bot_message, so an integration posted it. A message of a person states no subtype. | `$observed.message_readback.messages[0].subtype` | `eq` | `bot_message` | `message_readback` |
| `message_posted_in_the_window` | Slack posted the message after the operation started. The ts of a message is a Unix timestamp, and the bounds oldest and latest read the same values. | `$observed.message_readback.messages[0].ts` | `time_after` | `$operation.created_at` | `message_readback` |
| `message_event_names_the_conversation` | At least one message.channels event of this message names the conversation that the intent stated. The answer of conversations.history names no conversation, so this event is the only read that proves it. A workspace with no event subscription cannot verify this level. | `$observed.message_posted_event.count` | `gte` | `1` | `message_posted_event` |

## Which conditions give CONTRADICTED?

| Condition | Class | Reason | Path | Operator | Expected |
| --- | --- | --- | --- | --- | --- |
| `message_text_differs` | wrong subject | Slack holds a different text than the intent stated. Ask a person. | `$observed.message_readback.messages[0].text` | `ne` | `$input.text` |
| `message_predates_operation` | pre existing state | The message is older than the operation. It proves nothing. | `$observed.message_readback.messages[0].ts` | `time_before` | `$operation.created_at` |
| `duplicate_message_present` | duplicate side effect | Slack holds more than one message of this text in this conversation since the operation started. Do not retry. | `$observed.message_conversation_posts.count` | `gt` | `1` |

## Which observed states map to a verdict before completion?

| Rule | Match | Verdict | Reason |
| --- | --- | --- | --- |
| `read_refused` | `$observed.message_readback.ok` eq `false` | UNVERIFIABLE | Slack answered the read with the status code 200 and the member ok false. The answer is a refusal and it is no evidence. |

## How long does the runtime observe?

| Timing member | Value |
| --- | --- |
| Initial delay | 1000 ms |
| Poll interval | 5000 ms |
| Backoff | exponential factor 2, max 60000 ms |
| Maximum attempts | 40 |
| Timeout | 900000 ms |
| Stale read window | 20000 ms |
| On timeout | UNVERIFIABLE (`evidence_unavailable_before_timeout`), escalated to a person |

## Where do these rules come from?

- `slack.openapi#/paths/~1chat.postMessage/post`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.openapi#/paths/~1chat.postMessage/post/responses/200`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.docs.post_message#post-a-message`: [slack.docs.post_message](https://docs.slack.dev/reference/methods/chat.postMessage), retrieved 2026-09-08
- `slack.docs.conversations#conversations/read-one-message`: [slack.docs.conversations](https://docs.slack.dev/reference/methods/conversations.history), retrieved 2026-09-08
- `slack.openapi#/components/schemas/ConversationsHistoryResponse/properties/ok`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.docs.web_api#web-api/the-ok-member`: [slack.docs.web_api](https://docs.slack.dev/apis/web-api/), retrieved 2026-09-08
- `slack.docs.web_api#web-api/a-failure-carries-the-status-code-200`: [slack.docs.web_api](https://docs.slack.dev/apis/web-api/), retrieved 2026-09-08
- `slack.openapi#/components/schemas/ConversationsHistoryResponse/properties/messages`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.docs.conversations#conversations/a-hidden-message`: [slack.docs.conversations](https://docs.slack.dev/reference/methods/conversations.history), retrieved 2026-09-08
- `slack.openapi#/components/schemas/Message/properties/ts`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.openapi#/components/schemas/ChatPostMessageResponse/properties/ts`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.openapi#/components/schemas/Message/properties/text`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.docs.post_message#post-a-message/the-response`: [slack.docs.post_message](https://docs.slack.dev/reference/methods/chat.postMessage), retrieved 2026-09-08
- `slack.openapi#/components/schemas/Message/properties/subtype`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08
- `slack.docs.post_message#post-a-message/the-subtype-of-a-message-that-an-app-posts`: [slack.docs.post_message](https://docs.slack.dev/reference/methods/chat.postMessage), retrieved 2026-09-08
- `slack.docs.conversations#conversations/read-the-history-of-a-conversation`: [slack.docs.conversations](https://docs.slack.dev/reference/methods/conversations.history), retrieved 2026-09-08
- `slack.events#/events/message.channels`: [slack.events](https://docs.slack.dev/reference/events/message), retrieved 2026-09-08
- `slack.docs.events#events-api/the-message-subscriptions`: [slack.docs.events](https://docs.slack.dev/apis/events-api/), retrieved 2026-09-08
- `slack.openapi#/components/schemas/ChatPostMessageResponse/properties/channel`: [slack.openapi](https://docs.slack.dev/reference/methods), retrieved 2026-09-08

### Can slack.message.posted return VERIFIED from the action response alone?

No. The minimum evidence level is E2. The action response is E1. The completion conditions read `message_readback` and `message_posted_event`.

### What happens after the timeout?

The verdict is UNVERIFIABLE with the reason `evidence_unavailable_before_timeout`. The operation goes to a person for review.

## Read next

- [Read the Slack Web API verification page](https://provely.sh/verify/slack)
- [Read the slack.direct_message.posted contract](https://provely.sh/verify/slack/slack.direct_message.posted)
- [Read what a completion contract contains](https://provely.sh/contracts)
- [Read what a receipt proves](https://provely.sh/docs/receipts)
