{
  "schemaVersion": "1.0",
  "title": "Chatwoot webhook security, loop prevention, and replay",
  "description": "This reference defines the complete inbound boundary implemented by the reviewed Chatwoot host. It covers authentication, freshness, replay state, loop suppression, event identity, local persistence, and central publication.",
  "canonical": "https://getaip.org/docs/connectors/chatwoot/reference/webhook-security-loop-prevention-and-replay",
  "route": "/docs/connectors/chatwoot/reference/webhook-security-loop-prevention-and-replay",
  "source": "docs/connectors/chatwoot/reference/webhook-security-loop-prevention-and-replay.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/chatwoot/reference/webhook-security-loop-prevention-and-replay.md",
    "txt": "/docs/download/connectors/chatwoot/reference/webhook-security-loop-prevention-and-replay.txt",
    "json": "/docs/download/connectors/chatwoot/reference/webhook-security-loop-prevention-and-replay.json",
    "pdf": "/docs/download/connectors/chatwoot/reference/webhook-security-loop-prevention-and-replay.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Chatwoot webhook security, loop prevention, and replay\ndescription: >-\n  Verify exact Chatwoot webhook bytes, fence delivery identities, suppress\n  connector loops, and retain deterministic AIP events\nkind: reference\naudience: operator\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: chatwoot\naliases:\n  - Chatwoot webhook HMAC reference\n  - Chatwoot webhook replay and event delivery\n---\n\n# Chatwoot webhook security, loop prevention, and replay\n\nThis reference defines the complete inbound boundary implemented by the\nreviewed Chatwoot host. It covers authentication, freshness, replay state, loop\nsuppression, event identity, local persistence, and central publication.\n\nA successful webhook response proves local acceptance or deliberate\nsuppression. It does not always prove that central AIP acknowledged or consumed\nthe event.\n\n## Ingress boundary\n\nWebhook ingress is opt-in. The standalone host exposes:\n\n```text\nPOST /webhooks/chatwoot\n```\n\nonly when `AIP_CHATWOOT_WEBHOOK_SECRET_FILE` is configured. In that mode,\n`AIP_CONNECTOR_HOST_EVENT_ENDPOINT` is also required. An incomplete\nconfiguration fails startup.\n\nIf the webhook secret is omitted, the host is outbound-only and does not\ncreate this route. Provider webhook records do not change that host behavior.\n\nThe route accepts an original body of at most 1 MiB. The bytes must be valid\nUTF-8 because the frozen delivery type stores the exact body as a string before\nverification and JSON parsing.\n\n## Acceptance pipeline\n\nOne delivery follows this order:\n\n1. read the three required headers;\n2. parse the timestamp as a signed integer;\n3. retain the exact UTF-8 request body;\n4. verify timestamp freshness and HMAC over the raw body;\n5. atomically observe the delivery ID in instance-scoped replay state;\n6. parse the raw body as the supported Chatwoot subset;\n7. suppress connector-originated loop messages;\n8. map a non-looping delivery to one Channel Message;\n9. derive one deterministic AIP Event ID;\n10. append or recover that event in the local event log;\n11. durably enqueue the event for central AIP;\n12. return the local and central acceptance outcome.\n\nSignature verification happens before JSON parsing. Reformatting, parsing, or\nre-serializing the body before HMAC verification changes the signed bytes and\nis invalid.\n\n## Required headers\n\n| HTTP header | Accepted value | Used for |\n|---|---|---|\n| `X-Chatwoot-Delivery` | Non-empty string | Replay identity and deterministic Event ID |\n| `X-Chatwoot-Timestamp` | Base-10 signed integer | Freshness and signed input |\n| `X-Chatwoot-Signature` | `sha256=<hex>` | HMAC-SHA256 comparison |\n\nHTTP header names are case-insensitive. Missing, non-text, or empty headers\nproduce `400 webhook.missing_header`. A non-integer timestamp produces\n`400 webhook.invalid_timestamp`.\n\nThe connector does not derive the delivery identity from the JSON body. Keep\nthe provider-supplied delivery header unchanged across a legitimate retry.\n\n## Signature construction\n\nLet:\n\n- `secret` be the exact bytes from the owner-only webhook-secret file;\n- `timestamp` be the decimal header text after integer parsing;\n- `raw_body` be the exact request body bytes.\n\nThe signed byte sequence is:\n\n```text\nascii(decimal_timestamp) || \".\" || raw_body\n```\n\nThe signature value is:\n\n```text\n\"sha256=\" || lowercase_or_uppercase_hex(HMAC-SHA256(secret, signed_bytes))\n```\n\nHex decoding accepts valid hex digits; the prefix itself is exactly\n`sha256=`. The computed and supplied MACs are compared in constant time after\ntheir lengths match.\n\nThe host accepts a timestamp at most 300 seconds in the past or future relative\nto its UTC clock. A larger absolute difference fails authentication.\n\nClock synchronization is therefore part of availability. Do not widen the\nwindow in a proxy or replace the timestamp before forwarding, because either\nchange breaks the source-owned verification contract.\n\n## Raw-body rules\n\nAuthentication binds exact bytes, not a JSON value. These bodies can represent\nthe same JSON while producing different signatures:\n\n- different whitespace;\n- different member order;\n- different string escapes;\n- a final newline;\n- a different Unicode byte representation.\n\nA reverse proxy can terminate TLS, but it must preserve the body bytes and the\nthree headers. Content transformation, decompression policy, and request-size\nlimits must be tested against this byte-level boundary.\n\nThe connector parses only after successful HMAC verification. The supported\ntop-level subset requires `event`, `account`, and `conversation`; `message` and\n`contact` are optional.\n\n## Replay state is a repair fence\n\nThe production host builds a `ProfileStateChatwootReplayStore` scoped as:\n\n```text\ninstance:{connector_instance_id}\n```\n\nThe state namespace is\n`aip.connector.chatwoot.webhook_replay.v1`. Compare-and-set gives a shared\ninstance one atomic delivery-ID set across replicas using the same durable\nprofile state.\n\n| Property | Reviewed value |\n|---|---:|\n| Freshness and retention cutoff | 300 seconds |\n| Maximum retained delivery identities | 10,000 |\n| Compare-and-set attempts | 32 |\n| Production scope | Logical connector instance |\n\nBefore inserting a new ID, the store removes entries whose recorded delivery\ntimestamp is older than the current cutoff. At capacity, it removes the oldest\nrecorded timestamps until the new ID fits.\n\nA duplicate ID returns `false` from the replay store. The ingestion path\ndeliberately does not reject or drop on that Boolean. It reconstructs the same\ndeterministic Event identity so a provider retry can repair a crash between\nreplay observation and event persistence.\n\nThis distinction matters:\n\n- replay state records that an authenticated delivery was observed;\n- deterministic Event identity prevents a second durable AIP event;\n- the event store returns the first stored event when that ID already exists;\n- the durable publication outbox can retry central delivery independently.\n\nThe error variant `connector.chatwoot.webhook_replay` exists in the connector\nerror mapping, but the reviewed delivery path does not construct it for an\nordinary duplicate.\n\n## Deterministic Event identity\n\nA non-looping delivery becomes an Event with:\n\n| Field | Mapping |\n|---|---|\n| `kind` | `chatwoot.channel_message` |\n| `id` | `evt_chatwoot_` plus lowercase SHA-256 hex of the delivery ID |\n| local `actor` | Principal derived from Chatwoot account and contact IDs |\n| `data.channel_message` | Normalized Channel Message |\n| delivery metadata | Original delivery ID |\n| raw payload summary | Chatwoot event name |\n\nThe Channel Message carries the provider conversation as an external\nreference, the message object, text content, and a Chatwoot channel descriptor.\nMissing numeric account, conversation, or contact IDs map to the string\n`unknown` rather than creating provider facts.\n\nBefore central publication, the signed connector-host publisher moves the\nprovider-originated actor into upstream data. The authenticated central ingress\nowns the protocol Event actor and binds it to the host signing identity.\n\n## Loop prevention\n\nMessages created by the three connector composites contain:\n\n```json\n{\n  \"content_attributes\": {\n    \"aip\": {\n      \"connector_id\": \"chatwoot\",\n      \"loop_prevention\": true,\n      \"action_id\": \"act_...\"\n    }\n  }\n}\n```\n\nInbound mapping suppresses a message when either condition is true:\n\n- `content_attributes.aip.loop_prevention` is Boolean `true`;\n- `content_attributes.aip.connector_id` equals `chatwoot`.\n\nSuppression happens after authentication, replay observation, and JSON parsing,\nbut before Channel Message and Event construction. The host returns:\n\n```json\n{\n  \"accepted_events\": 0,\n  \"central_delivery\": null\n}\n```\n\nNo AIP event or central outbox record is created for that delivery.\n\nLoop prevention is marker-based. It is not a general bot-sender rule and does\nnot prove that unmarked automations are safe to reprocess.\n\n## Local persistence and central publication\n\nFor a non-looping delivery, the host first calls the local Event Log's atomic\n`append_with_outcome`. The durable PostgreSQL implementation claims unique\nEvent IDs and returns the existing stored event for an idempotent replay.\n\nThe host then passes the exact accepted Event to the connector event publisher\non channel `chatwoot-webhooks`. The publisher:\n\n- validates the admitted channel route;\n- normalizes the Event for signed publication;\n- validates bounded Event size;\n- stores a content-addressed batch in durable profile state;\n- attempts immediate delivery to the fixed central endpoint;\n- retains transient failures for the background publisher.\n\nThe outbox accepts one through 100 Events per batch and retains at most 10,000\npending batches. The Chatwoot route submits one Event at a time.\n\n## Successful responses\n\nFor a non-looping delivery, HTTP `200` returns one of:\n\n```json\n{\n  \"accepted_events\": 1,\n  \"central_delivery\": \"centrally_acknowledged\"\n}\n```\n\n```json\n{\n  \"accepted_events\": 1,\n  \"central_delivery\": \"durably_queued\"\n}\n```\n\n| `central_delivery` | Established fact |\n|---|---|\n| `centrally_acknowledged` | Central AIP acknowledged the batch before the webhook response |\n| `durably_queued` | Local durable outbox owns later central delivery |\n| `null` | No Event was produced, normally because loop prevention matched |\n\n`durably_queued` is a successful local acceptance. It does not prove central\nconsumption, subscriber processing, or business completion.\n\n## Error responses\n\n| HTTP status and code | Boundary that failed | Retry decision |\n|---|---|---|\n| `400 webhook.missing_header` | Required header absent, empty, or not text | Correct sender or proxy; do not invent a header |\n| `400 webhook.invalid_timestamp` | Timestamp was not an integer | Correct header encoding |\n| `400 webhook.invalid_utf8` | Body was not valid UTF-8 | Preserve evidence and correct sender encoding |\n| `401 webhook.authentication_failed` | HMAC verification, JSON decoding, or connector-level webhook mapping returned `InvalidWebhook` | Distinguish clock, bytes, signature, secret, and payload shape in bounded host diagnostics; do not bypass verification |\n| `400 webhook.invalid_delivery` | Another connector-level delivery rejection occurred | Preserve delivery ID and inspect bounded host diagnostics |\n| `500 webhook.invalid_mapping` | Channel Message or Event mapping failed | Preserve authenticated delivery evidence and escalate |\n| `503 webhook.storage_unavailable` | Replay state, Event Log, or durable outbox failed | Repair durable state, then replay the same provider delivery |\n\nThe public HTTP mapping deliberately groups several internal errors. Never put\nthe secret, raw customer payload, or unrestricted provider message into an\nerror response.\n\n## Crash-window recovery\n\n| Last established point | Provider retry with same delivery ID |\n|---|---|\n| HMAC failed | Fails again until clock, bytes, signature, or secret is corrected |\n| HMAC passed; replay state failed | Can be retried after durable state recovers |\n| Replay ID stored; process crashed before Event append | Reconstructs the deterministic Event ID and can append it |\n| Event appended; process crashed before outbox enqueue | Returns the existing Event and can enqueue it |\n| Outbox durable; central delivery failed | Background publisher owns retry; provider retry is not required |\n| Central acknowledged; local outbox cleanup crashed | Central Event identity deduplicates a repeated signed batch |\n| Loop message suppressed | Repeats the same zero-Event outcome while the signature remains fresh |\n\nUse the same provider delivery ID, timestamp, signature, and exact raw body\nwhen replaying within the authentication window. A newly signed delivery with a\nnew ID is a new ingress identity, even when its JSON content matches.\n\nDo not delete replay state or local Event data to force acceptance. Repair the\nfailed durable component and let deterministic identities settle the original\ndelivery.\n\n## Verification checklist\n\nVerify all of the following for a webhook-enabled deployment:\n\n- the secret is an owner-only file and is not present in environment values;\n- the central event endpoint is fixed, allowlisted, and signed by the host;\n- the public proxy preserves required headers and exact body bytes;\n- host and provider clocks remain within the five-minute boundary;\n- all replicas for one logical instance share durable profile state;\n- Event storage enforces durable Event-ID uniqueness;\n- `durably_queued` is monitored as pending delivery, not central success;\n- looped connector messages return zero accepted Events;\n- retries retain the original delivery identity and raw bytes;\n- logs, metrics, and retained evidence redact customer payloads and secrets.\n\nThese checks establish conformance to the reviewed source path. They do not\nprove provider reachability, live delivery, or production qualification.\n\n## Related documentation\n\n- [Chatwoot connector configuration](configuration.md)\n- [Inboxes, integrations, and webhooks](../capabilities/inboxes-integrations-and-webhooks.md)\n- [AIP composite operations](../capabilities/aip-composite-operations.md)\n- [Connector fleet architecture](../../../architecture/connector-fleet.md)\n- [Error reference](../../../reference/errors.md)\n",
    "text": "Chatwoot webhook security, loop prevention, and replay\n\nThis reference defines the complete inbound boundary implemented by the\nreviewed Chatwoot host. It covers authentication, freshness, replay state, loop\nsuppression, event identity, local persistence, and central publication.\n\nA successful webhook response proves local acceptance or deliberate\nsuppression. It does not always prove that central AIP acknowledged or consumed\nthe event.\n\nIngress boundary\n\nWebhook ingress is opt-in. The standalone host exposes:\n\nPOST /webhooks/chatwoot\n\nonly when AIPCHATWOOTWEBHOOKSECRETFILE is configured. In that mode,\nAIPCONNECTORHOSTEVENTENDPOINT is also required. An incomplete\nconfiguration fails startup.\n\nIf the webhook secret is omitted, the host is outbound-only and does not\ncreate this route. Provider webhook records do not change that host behavior.\n\nThe route accepts an original body of at most 1 MiB. The bytes must be valid\nUTF-8 because the frozen delivery type stores the exact body as a string before\nverification and JSON parsing.\n\nAcceptance pipeline\n\nOne delivery follows this order:\n1. read the three required headers;\n2. parse the timestamp as a signed integer;\n3. retain the exact UTF-8 request body;\n4. verify timestamp freshness and HMAC over the raw body;\n5. atomically observe the delivery ID in instance-scoped replay state;\n6. parse the raw body as the supported Chatwoot subset;\n7. suppress connector-originated loop messages;\n8. map a non-looping delivery to one Channel Message;\n9. derive one deterministic AIP Event ID;\n10. append or recover that event in the local event log;\n11. durably enqueue the event for central AIP;\n12. return the local and central acceptance outcome.\n\nSignature verification happens before JSON parsing. Reformatting, parsing, or\nre-serializing the body before HMAC verification changes the signed bytes and\nis invalid.\n\nRequired headers\n\n| HTTP header | Accepted value | Used for |\n\n| X-Chatwoot-Delivery | Non-empty string | Replay identity and deterministic Event ID |\n| X-Chatwoot-Timestamp | Base-10 signed integer | Freshness and signed input |\n| X-Chatwoot-Signature | sha256= | HMAC-SHA256 comparison |\n\nHTTP header names are case-insensitive. Missing, non-text, or empty headers\nproduce 400 webhook.missingheader. A non-integer timestamp produces\n400 webhook.invalidtimestamp.\n\nThe connector does not derive the delivery identity from the JSON body. Keep\nthe provider-supplied delivery header unchanged across a legitimate retry.\n\nSignature construction\n\nLet:\n• secret be the exact bytes from the owner-only webhook-secret file;\n• timestamp be the decimal header text after integer parsing;\n• rawbody be the exact request body bytes.\n\nThe signed byte sequence is:\n\nascii(decimaltimestamp) || \".\" || rawbody\n\nThe signature value is:\n\n\"sha256=\" || lowercaseoruppercasehex(HMAC-SHA256(secret, signedbytes))\n\nHex decoding accepts valid hex digits; the prefix itself is exactly\nsha256=. The computed and supplied MACs are compared in constant time after\ntheir lengths match.\n\nThe host accepts a timestamp at most 300 seconds in the past or future relative\nto its UTC clock. A larger absolute difference fails authentication.\n\nClock synchronization is therefore part of availability. Do not widen the\nwindow in a proxy or replace the timestamp before forwarding, because either\nchange breaks the source-owned verification contract.\n\nRaw-body rules\n\nAuthentication binds exact bytes, not a JSON value. These bodies can represent\nthe same JSON while producing different signatures:\n• different whitespace;\n• different member order;\n• different string escapes;\n• a final newline;\n• a different Unicode byte representation.\n\nA reverse proxy can terminate TLS, but it must preserve the body bytes and the\nthree headers. Content transformation, decompression policy, and request-size\nlimits must be tested against this byte-level boundary.\n\nThe connector parses only after successful HMAC verification. The supported\ntop-level subset requires event, account, and conversation; message and\ncontact are optional.\n\nReplay state is a repair fence\n\nThe production host builds a ProfileStateChatwootReplayStore scoped as:\n\ninstance:{connectorinstanceid}\n\nThe state namespace is\naip.connector.chatwoot.webhookreplay.v1. Compare-and-set gives a shared\ninstance one atomic delivery-ID set across replicas using the same durable\nprofile state.\n\n| Property | Reviewed value |\n\n| Freshness and retention cutoff | 300 seconds |\n| Maximum retained delivery identities | 10,000 |\n| Compare-and-set attempts | 32 |\n| Production scope | Logical connector instance |\n\nBefore inserting a new ID, the store removes entries whose recorded delivery\ntimestamp is older than the current cutoff. At capacity, it removes the oldest\nrecorded timestamps until the new ID fits.\n\nA duplicate ID returns false from the replay store. The ingestion path\ndeliberately does not reject or drop on that Boolean. It reconstructs the same\ndeterministic Event identity so a provider retry can repair a crash between\nreplay observation and event persistence.\n\nThis distinction matters:\n• replay state records that an authenticated delivery was observed;\n• deterministic Event identity prevents a second durable AIP event;\n• the event store returns the first stored event when that ID already exists;\n• the durable publication outbox can retry central delivery independently.\n\nThe error variant connector.chatwoot.webhookreplay exists in the connector\nerror mapping, but the reviewed delivery path does not construct it for an\nordinary duplicate.\n\nDeterministic Event identity\n\nA non-looping delivery becomes an Event with:\n\n| Field | Mapping |\n\n| kind | chatwoot.channelmessage |\n| id | evtchatwoot plus lowercase SHA-256 hex of the delivery ID |\n| local actor | Principal derived from Chatwoot account and contact IDs |\n| data.channelmessage | Normalized Channel Message |\n| delivery metadata | Original delivery ID |\n| raw payload summary | Chatwoot event name |\n\nThe Channel Message carries the provider conversation as an external\nreference, the message object, text content, and a Chatwoot channel descriptor.\nMissing numeric account, conversation, or contact IDs map to the string\nunknown rather than creating provider facts.\n\nBefore central publication, the signed connector-host publisher moves the\nprovider-originated actor into upstream data. The authenticated central ingress\nowns the protocol Event actor and binds it to the host signing identity.\n\nLoop prevention\n\nMessages created by the three connector composites contain:\n\n{\n  \"contentattributes\": {\n    \"aip\": {\n      \"connectorid\": \"chatwoot\",\n      \"loopprevention\": true,\n      \"actionid\": \"act...\"\n    }\n  }\n}\n\nInbound mapping suppresses a message when either condition is true:\n• contentattributes.aip.loopprevention is Boolean true;\n• contentattributes.aip.connectorid equals chatwoot.\n\nSuppression happens after authentication, replay observation, and JSON parsing,\nbut before Channel Message and Event construction. The host returns:\n\n{\n  \"acceptedevents\": 0,\n  \"centraldelivery\": null\n}\n\nNo AIP event or central outbox record is created for that delivery.\n\nLoop prevention is marker-based. It is not a general bot-sender rule and does\nnot prove that unmarked automations are safe to reprocess.\n\nLocal persistence and central publication\n\nFor a non-looping delivery, the host first calls the local Event Log's atomic\nappendwithoutcome. The durable PostgreSQL implementation claims unique\nEvent IDs and returns the existing stored event for an idempotent replay.\n\nThe host then passes the exact accepted Event to the connector event publisher\non channel chatwoot-webhooks. The publisher:\n• validates the admitted channel route;\n• normalizes the Event for signed publication;\n• validates bounded Event size;\n• stores a content-addressed batch in durable profile state;\n• attempts immediate delivery to the fixed central endpoint;\n• retains transient failures for the background publisher.\n\nThe outbox accepts one through 100 Events per batch and retains at most 10,000\npending batches. The Chatwoot route submits one Event at a time.\n\nSuccessful responses\n\nFor a non-looping delivery, HTTP 200 returns one of:\n\n{\n  \"acceptedevents\": 1,\n  \"centraldelivery\": \"centrallyacknowledged\"\n}\n\n{\n  \"acceptedevents\": 1,\n  \"centraldelivery\": \"durablyqueued\"\n}\n\n| centraldelivery | Established fact |\n\n| centrallyacknowledged | Central AIP acknowledged the batch before the webhook response |\n| durablyqueued | Local durable outbox owns later central delivery |\n| null | No Event was produced, normally because loop prevention matched |\n\ndurablyqueued is a successful local acceptance. It does not prove central\nconsumption, subscriber processing, or business completion.\n\nError responses\n\n| HTTP status and code | Boundary that failed | Retry decision |\n\n| 400 webhook.missingheader | Required header absent, empty, or not text | Correct sender or proxy; do not invent a header |\n| 400 webhook.invalidtimestamp | Timestamp was not an integer | Correct header encoding |\n| 400 webhook.invalidutf8 | Body was not valid UTF-8 | Preserve evidence and correct sender encoding |\n| 401 webhook.authenticationfailed | HMAC verification, JSON decoding, or connector-level webhook mapping returned InvalidWebhook | Distinguish clock, bytes, signature, secret, and payload shape in bounded host diagnostics; do not bypass verification |\n| 400 webhook.invaliddelivery | Another connector-level delivery rejection occurred | Preserve delivery ID and inspect bounded host diagnostics |\n| 500 webhook.invalidmapping | Channel Message or Event mapping failed | Preserve authenticated delivery evidence and escalate |\n| 503 webhook.storageunavailable | Replay state, Event Log, or durable outbox failed | Repair durable state, then replay the same provider delivery |\n\nThe public HTTP mapping deliberately groups several internal errors. Never put\nthe secret, raw customer payload, or unrestricted provider message into an\nerror response.\n\nCrash-window recovery\n\n| Last established point | Provider retry with same delivery ID |\n\n| HMAC failed | Fails again until clock, bytes, signature, or secret is corrected |\n| HMAC passed; replay state failed | Can be retried after durable state recovers |\n| Replay ID stored; process crashed before Event append | Reconstructs the deterministic Event ID and can append it |\n| Event appended; process crashed before outbox enqueue | Returns the existing Event and can enqueue it |\n| Outbox durable; central delivery failed | Background publisher owns retry; provider retry is not required |\n| Central acknowledged; local outbox cleanup crashed | Central Event identity deduplicates a repeated signed batch |\n| Loop message suppressed | Repeats the same zero-Event outcome while the signature remains fresh |\n\nUse the same provider delivery ID, timestamp, signature, and exact raw body\nwhen replaying within the authentication window. A newly signed delivery with a\nnew ID is a new ingress identity, even when its JSON content matches.\n\nDo not delete replay state or local Event data to force acceptance. Repair the\nfailed durable component and let deterministic identities settle the original\ndelivery.\n\nVerification checklist\n\nVerify all of the following for a webhook-enabled deployment:\n• the secret is an owner-only file and is not present in environment values;\n• the central event endpoint is fixed, allowlisted, and signed by the host;\n• the public proxy preserves required headers and exact body bytes;\n• host and provider clocks remain within the five-minute boundary;\n• all replicas for one logical instance share durable profile state;\n• Event storage enforces durable Event-ID uniqueness;\n• durablyqueued is monitored as pending delivery, not central success;\n• looped connector messages return zero accepted Events;\n• retries retain the original delivery identity and raw bytes;\n• logs, metrics, and retained evidence redact customer payloads and secrets.\n\nThese checks establish conformance to the reviewed source path. They do not\nprove provider reachability, live delivery, or production qualification.\n\nRelated documentation\n• Chatwoot connector configuration (configuration.md)\n• Inboxes, integrations, and webhooks (../capabilities/inboxes-integrations-and-webhooks.md)\n• AIP composite operations (../capabilities/aip-composite-operations.md)\n• Connector fleet architecture (../../../architecture/connector-fleet.md)\n• Error reference (../../../reference/errors.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "ed8f157e243abb07bddd6d29ec53d029e81058d125ac089443cde4935a205bd8"
  }
}
