Chattree features an integrated inbound webhook and local event processing pipeline. It enables third-party desktop tools, local automations, and WebAssembly plugins to securely deliver external triggers, notifications, and event streams directly into the Chattree event runtime.
External Source (e.g., Local CLI / Dev Tool / Service)
│
▼ HTTP POST /plugins/<pluginId>/<path> (Headers: X-Chattree-Signature-256, X-Chattree-Delivery-Id)
┌─────────────────────────────────────────────────────────────┐
│ InboundHttpServer (127.0.0.1) │
│ - Loopback-only socket listener (default port: 8765) │
│ - Active in foreground; auto-resumes on app return │
│ - Concurrent request throttling & rate limiting │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ InboundHttpRouter │
│ - WebhookSsrfGuard: blocks non-loopback / unsafe targets │
│ - InboundEndpointResolver: authenticates endpoint token │
│ - WebhookSigner: validates HMAC-SHA256 signature │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ EventJournal │
│ - Persistent SQLite storage for events and receipts │
│ - Idempotency deduplication by (endpointId, deliveryId) │
│ - Delivery status tracking: pending, dispatched, failed │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ HostEventBus & EventDispatcher │
│ - EventSubscriptionRegistry: maps event types to consumers │
│ - Permission check: verifies network.webhook_inbound │
│ - Dispatches to targetAction in WASM plugin runtime │
└─────────────────────────────────────────────────────────────┘
The local webhook listener is powered by InboundHttpServer.
FOREGROUND_SERVICE and FOREGROUND_SERVICE_SPECIAL_USE permissions have been completely removed from AndroidManifest.xml.WidgetsBindingObserver): The server actively monitors Android application lifecycle states:
paused, inactive, detached, or hidden, the server immediately tears down its TCP sockets (stop(preserveLifecycleObserver: true)), leaving 0 background sockets and 0 background processes (startForegroundCount = 0).resumed), the listener automatically re-binds to the configured port without requiring manual toggle or configuration.maxConcurrentRequests = 32). If the ceiling is reached, incoming requests receive an immediate HTTP 503 Service Unavailable with a Retry-After: 1 header, protecting mobile memory from connection floods.127.0.0.1 (InternetAddress.loopbackIPv4) and accepts IPv4/IPv6 loopback (127.0.0.1, ::1, [::1]). Users can enable Permitir dispositivos en red local (LAN) directly in Settings → Inbound Webhooks (or in Settings → Plugins → [Plugin] → Webhooks Entrantes) to bind to 0.0.0.0 and accept authenticated requests from RFC 1918 LAN addresses (10.x, 172.16–31.x, 192.168.x) and IPv6 ULA (fc00::/7).8765. Users can configure any custom port (0–65535) in Settings → Inbound Webhooks.2xx requests ingested by InboundHttpRouter immediately trigger EventRuntime.instance.processDueDeliveries() so subscribed WASM handlers execute without polling latency./plugins/system/test): InboundHttpRouter natively supports authenticated diagnostic pings on /plugins/system/test (matching the cloud relay behavior) so users can verify local and LAN connectivity even before installing a third-party webhook plugin.InboundHttpServerState.error), reports the conflict in Settings, and allows the user to specify an alternative port without crashing app startup.The inbound pipeline implements defense-in-depth:
WebhookSsrfGuard — P2.5)Prevents server-side request forgery by ensuring all internal routing targets adhere to strict loopback or explicitly authorized LAN constraints:
WebhookSsrfGuard blocks all IANA special-purpose IPv4 and IPv6 ranges:
0.0.0.0/8, 10.0.0.0/8, Carrier-Grade NAT (100.64.0.0/10), Loopback (127.0.0.0/8), Link-Local (169.254.0.0/16), 172.16.0.0/12, IETF Protocol Assignments (192.0.0.0/24), TEST-NET-1/2/3 (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), 6to4 Relay (192.88.99.0/24), Private (192.168.0.0/16), Benchmarking (198.18.0.0/15), Multicast (224.0.0.0/4), Reserved (240.0.0.0/4), Limited Broadcast (255.255.255.255/32).::1/128), Unspecified (::/128), IPv4-mapped (::ffff:0:0/96), IPv4-IPv6 translation (64:ff9b::/96, 64:ff9b:1::/48), Discard-only (100::/64), Teredo (2001::/32), BMWG (2001:2::/48), ORCHIDv2 (2001:10::/28, 2001:20::/28), Documentation (2001:db8::/32), 6to4 (2002::/16), ULA (fc00::/7 blocked unless LAN mode enabled), Link-Local (fe80::/10), Multicast (ff00::/8).connectWithDnsFallback): When a target resolves to multiple IP addresses, Chattree validates each individual IP against the SSRF guard before attempting connection. If a valid IP fails to connect, it falls back to the next valid IP, ensuring that DNS round-robin or multi-homed services work reliably without ever contacting an unauthorized private IP.bearer): Validates Authorization: Bearer <token> against the active (or rotating) endpoint secret stored in WebhookSecretStore.hmacSha256Raw): Validates X-Chattree-Signature-256: sha256=<hex_digest> computed over the raw UTF-8 request body using the UTF-8 encoded endpoint secret.WebhookSigner.secureSignatureCompare) to prevent side-channel timing attacks.sanitizeActivityPath): Prohibits path traversal attacks (..), percent-encoded path evasion (%), and query strings.stale_relay_message.X-Chattree-Delivery-Id: <unique-id>). Receipts are stored in EventJournal to prevent duplicate execution of plugin handlers.EventJournal)All incoming webhook events are recorded in an embedded SQLite database before dispatch:
InboundEvent is created and committed to the database.pending.EventDispatcher pulls pending events and dispatches them to registered consumers. If a plugin handler fails, exponential backoff retries are tracked in the database.Users have full control over both the global listener and per-plugin webhook endpoints:
Permitir dispositivos en red local (LAN)): Switches the listener between loopback-only (127.0.0.1) and local network binding (0.0.0.0) directly from the global server card.8765).http://127.0.0.1:8765 or http://0.0.0.0:8765) along with the active plugin webhook path (or /plugins/system/test diagnostic route).Activo, Detenido, Iniciando, Error) with actionable error messages when conflicts occur.For any plugin that declares inboundWebhooks or network.webhook_inbound, the plugin detail screen displays a dedicated Webhooks Entrantes section where users can:
InboundHttpServer between loopback-only (127.0.0.1) and local network binding (0.0.0.0).http://127.0.0.1:<port><path>) and copy it to the clipboard.Bearer or HMAC-SHA256) and copy the active secret token stored in WebhookSecretStore.beginRotation + finalizeRotation), immediately generating a fresh 256-bit secret.WASM plugins consume inbound webhooks by declaring schemaVersion: 2 (or 1), the network.webhook_inbound capability, and inboundWebhooks / eventConsumers in plugin.json.
plugin.json):{
"schemaVersion": 2,
"id": "com.example.notifier",
"version": "1.0.0",
"name": "Local Notifier",
"description": "Receives local webhook alerts.",
"author": "Example Dev",
"primaryCommand": "notifier",
"type": "imported",
"runtime": "wasm",
"entrypoint": "run",
"wasmPath": "plugin.wasm",
"wasmSha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"runtimeApiVersion": "1.0.0",
"capabilities": [
"network.webhook_inbound",
"chattree.notes.create"
],
"tools": [
{
"id": "com.example.notifier.alert",
"command": "notifier",
"entrypoint": "run",
"displayName": "Local Notifier",
"description": "Processes incoming webhook and notification alerts.",
"capabilities": ["network.webhook_inbound", "chattree.notes.create"]
}
],
"inboundWebhooks": [
{
"id": "alert_hook",
"path": "/webhooks/com.example.notifier/alert_hook",
"authType": "bearer",
"eventType": "alert.critical"
}
],
"eventConsumers": [
{
"id": "alert_consumer",
"eventTypes": ["alert.critical"],
"targetAction": "com.example.notifier.alert",
"filter": {
"severity": ["high", "critical"]
},
"requiredPermissions": ["network.webhook_inbound", "chattree.notes.create"]
}
]
}
When an event arrives and matches the optional filter, Chattree invokes the tool specified in targetAction (com.example.notifier.alert -> exported run function), passing the serialized event envelope (eventId, type, source, timestamp, data). Event deliveries have a 45-second execution timeout (EventDispatcher.defaultHandlerTimeout) so handlers can safely invoke ai.complete (ai.context), chattree.notes.*, database.sql, or notifications.reply.
To receive webhooks 24/7 from external cloud platforms (such as GitHub, Stripe, GitLab, CI/CD pipelines, or remote IoT servers) without running battery-draining foreground services or violating Google Play background socket policies, Chattree introduces the Cloudflare Worker KV Store-and-Forward + FCM Architecture (P2.2):
External Cloud Emitter (GitHub / Stripe / IoT / CLI)
│
▼ HTTPS POST https://<worker>.<subdomain>.workers.dev/plugins/<pluginId>/<path>
┌─────────────────────────────────────────────────────────────┐
│ Personal Cloudflare Worker (Free) │
│ - Receives standard HTTP webhooks over the public web │
│ - Reads encrypted secrets (FCM_TOKEN, SERVICE_ACCOUNT) │
│ - Signs OAuth2 JWT and forwards via Google FCM HTTP v1 │
└──────────────────────────────┬──────────────────────────────┘
│
▼ FCM Data Push Message
┌─────────────────────────────────────────────────────────────┐
│ Google Play Services (FCM Push) │
└──────────────────────────────┬──────────────────────────────┘
│
▼ High-Priority Wake Signal
┌─────────────────────────────────────────────────────────────┐
│ Chattree on Android (Closed) │
│ - Lightweight isolate: fcmBackgroundHandler (entry point) │
│ - Zero heavy AI / WASM engine overhead │
│ - Appends raw message to pending inbox (SQLite/Prefs) │
│ - Fires native Android status bar notification │
└──────────────────────────────┬──────────────────────────────┘
│ User taps notification / opens app
▼
┌─────────────────────────────────────────────────────────────┐
│ FcmInboundRelayService │
│ - drainPendingInbox() runs on app resume / launch │
│ - Validates authentication token & idempotency key │
│ - Ingests into InboundWebhookService & EventJournal │
│ - Dispatches event to registered WASM plugin runtime │
└─────────────────────────────────────────────────────────────┘
v: '2', id, ts, path, sig). No payload bodies, authorization headers, or secrets ever travel across Firebase servers.502 Bad Gateway or 503 Service Unavailable. When Chattree receives and stores the payload, it sends an authenticated DELETE to purge the record from KV.fcmBackgroundHandler displays native status bar notifications localized in Spanish, English, or French.FirebaseMessaging.onMessage immediately triggers a heads-up visual notification (FlutterLocalNotificationsPlugin.show with Importance.high / Priority.high) and updates the Activity Log seamlessly.InboundEndpointResolver first before falling back to built-in diagnostic handlers, ensuring custom and official webhook plugins (such as com.developer.webhook.hub) execute their WASM handlers end-to-end.drainPendingInbox() processes pending events under an atomic concurrency lock (_drainInFlight) and caps unresolvable route retries (404) at 3 attempts to prevent infinite retry loops.Chattree features an automated in-app deployment workflow (CloudflareDeployService) allowing users to deploy and configure their personal relay Worker in seconds:
result[0]).chattree-relay-<hash> derived from installationId, preventing collisions or accidental deletion of other worker scripts in the user's account.obscureText: true by default (••••••••).FlutterSecureStorage) and strictly redacted from all system logs, error messages, and telemetry.GET /client/v4/accounts using the user's Cloudflare API Token.chattree-relay-<hash>-store.PUT /client/v4/accounts/{id}/workers/scripts/chattree-relay-<hash>.RELAY_ID: Unique cryptographic relay identifier.RELAY_SECRET: 256-bit cryptographic shared secret for HMAC signing and fetch authorization.FIREBASE_SERVICE_ACCOUNT: Firebase relay service account credentials integrated securely from device Keystore or compile-time configuration (never hardcoded in plain code).POST /scripts/{name}/subdomain.https://chattree-relay-<hash>.<subdomain>.workers.dev/r/<relayId>/plugins/<path>
InboundActivityStore — P2.4)To guarantee complete auditability, thread-safety, and crash resilience, Chattree includes a transactional activity log (Settings → Historial de actividad de webhooks):
InboundActivityStore (inbound_activity_v2.json) guarded by operating system-level file locking (FileLock.blockingExclusive) on inbound_activity_v2.lock. Eliminates all race conditions between the main UI isolate and background FCM isolates..tmp) and atomically renamed over the target file, ensuring zero corruption during sudden app termination or power loss.in_<microseconds>_<sequence>_<hash>_<path>
# Example: in_1794195920500235_12_3eef08_pluginssystemtest
/plugins/system/test): A dedicated diagnostic route processed directly by the core runtime. Users can send test pings at any time to verify cloud delivery and trigger native notifications without requiring any third-party plugin to be installed.A comprehensive architectural and security audit of the local and cloud webhook pipelines establishes the following production standards and operational guidelines:
| Dimension | Local Server (InboundHttpServer) | Cloud Relay (Cloudflare Worker + FCM) |
|---|---|---|
| Availability | Active in foreground only | 24/7 globally reachable |
| Network Scope | 127.0.0.1 (or local Wi-Fi LAN 0.0.0.0) | Public internet via HTTPS |
| Payload Ceiling | 256 KB (maxInboundHttpPayloadBytes) | ~3.5 KB direct / 256 KB with KV store-and-forward |
| Background Processing | Suspends when minimized (0 battery drain) | Wakes lightweight isolate, queues for app open |
| Latency | < 15 ms (direct socket) | 200 ms – 3 s (FCM delivery network) |
| Authentication | Bearer token / HMAC-SHA256 (constant-time) | Bearer / HMAC-SHA256 validated in device |
| Concurrency Guard | 32 concurrent requests (503 on saturation) | Serialized drain lock (_drainInFlight) |
FIREBASE_SERVICE_ACCOUNT) must never be committed in plain source code or bundled as plain constants.SecureCredentialStore.instance.save).--dart-define=FIREBASE_SERVICE_ACCOUNT_JSON=....wrangler secret put FIREBASE_SERVICE_ACCOUNT./plugins/system/test):
PreferencesService.getDiagnosticWebhookSecret()) across both local HTTP requests and cloud FCM relay messages./plugins/system/test are rejected immediately, preventing notification spam and status bar flooding.WebhookSigner.secureSignatureCompare to eliminate side-channel timing attacks.cachedToken). If Google FCM returns 401 Unauthorized (e.g., token revocation or clock skew), the Worker immediately invalidates cachedToken = null rather than serving stale tokens for up to 60 minutes.onTokenRefresh), the app flags the deployment as needing an update (last_deployed_fcm_token mismatch) and prompts the user to auto-sync the secret to Cloudflare.EventJournal.169.254.0.0/16) blocked...), query parameters, and fragmented URIs.(endpointId, idempotencyKey) before HTTP response.inbound_activity_v2.lock) with atomic replacement.$url = "https://chattree-relay-<hash>.<subdomain>.workers.dev/r/<relayId>/plugins/system/test"
$body = '{"event":"diagnostic_test","source":"powershell"}'
$headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer <DIAGNOSTIC_SECRET_COPIADO_DE_AJUSTES>"
}
Invoke-RestMethod -Uri $url -Method POST -Body $body -Headers $headers -ContentType "application/json"
# Reemplaza <IP_TELEFONO> con la IP local que muestra la app (ej. 192.168.1.100)
$url = "http://<IP_TELEFONO>:8765/plugins/system/test"
$body = '{"event":"local_lan_test"}'
$headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer <DIAGNOSTIC_SECRET_COPIADO_DE_AJUSTES>"
}
Invoke-RestMethod -Uri $url -Method POST -Body $body -Headers $headers -ContentType "application/json"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = "https://chattree-relay-<hash>.<subdomain>.workers.dev/r/<relayId>/plugins/com.example.webhook/alerts"
$headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer <TU_SECRET_DE_PLUGIN>"
"X-Chattree-Delivery-Id" = "deliv_$([DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds())"
}
$body = @{
event = "alert.critical"
title = "Prueba Cloudflare 24/7"
message = "Webhook recibido exitosamente a traves de Cloudflare KV y FCM!"
timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
} | ConvertTo-Json
Invoke-RestMethod -Uri $url -Method POST -Headers $headers -Body $body