2026 OpenClaw MeshMac: Zulip Incoming Webhook to Broadcast Multi-Node Build Status, Failure Summaries, and Bot URL Rotation
Published April 24, 2026
Meshmac Team
Engineering leads running a MeshMac pool in 2026 need one Zulip stream that stays accurate when jobs bounce between remote Macs. This guide is a minimal reproducible path: a single OpenClaw gateway owns the Zulip Webhook, builders enqueue normalized terminal events, operators manage egress allowlists and token-style URL rotation, and noisy retries collapse behind idempotency keys plus merged health alerts.
If you already ship chat from OpenClaw, treat Zulip like our Mattermost Incoming Webhook walkthrough or Webex multi-node webhook guide: same gateway-first posture, different host and JSON quirks. For cluster layout, start with the multi-node deployment guide and the OpenClaw hub. Prefer short markdown and log payload hashes instead of raw bodies.
Pain points this pattern removes
- Secret sprawl. Copying the Zulip integration URL onto every Mac multiplies leak paths and makes rotation chaotic.
- Ambiguous CI truth. Without a queue, two runners can race to post partial states during retries.
- Alert fatigue. Separate cards for probe failures, 429 storms, and build failures obscure the incident timeline.
Prepare the Zulip bot
Goal. Obtain one HTTPS URL whose query or path acts like a bearer credential—anyone who can POST it can post to your chosen stream or direct message target. That URL lives only on the OpenClaw gateway, not on pooled builders.
- Open Zulip as an organization administrator. Navigate to Personal / Organization settings → Integrations (wording varies slightly by server version), browse Incoming webhooks or add a dedicated incoming webhook bot, and pick the destination stream and default topic for CI—for example stream
builds, topicmeshmac. - Generate the integration URL once. Copy the full
https://URL Zulip shows in the setup dialog. If the UI offers a samplecurl, keep it as the authoritative body shape for that integration template. - Lock file permissions on the gateway. Store the URL at something like
/etc/openclaw/secrets.d/zulip/build-status.url, mode0440, root-owned, groupopenclaw-notify, following secrets and least privilege on MeshMac nodes.
Screenshot placement: Zulip integration dialog showing stream, topic, and the generated webhook URL (blur the key in published docs).
Probe from the gateway shell (replace the URL with your secret; use a scratch topic first):
sudo -u openclaw-notify -i
export ZULIP_INCOMING_WEBHOOK_URL='https://your-org.zulipchat.com/api/v1/external/your_integration?api_key=REDACTED'
curl -sS -w "\nhttp_code:%{http_code}\n" -X POST "$ZULIP_INCOMING_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
--data-binary '{"content":"**OpenClaw probe** — gateway egress OK"}'
If your integration template expects a different JSON field (for example Slack-shaped text), align the gateway mapper to the sample Zulip printed—do not guess on production.
Zulip bot URL rotation (treat like token rotation). When a URL leaks or a contractor offboards: generate a new integration URL in Zulip, swap the secret file on the gateway, reload the notifier, send a one-line probe, confirm delivery, then revoke or delete the old integration in Zulip. Document the owner and rollback order beside IM alerts and token rotation patterns.
Gateway outbound allowlist
Pick one sender. Exactly one OpenClaw gateway process should call Zulip. MeshMac workers publish build_finished records to a shared queue or RPC channel—see multi-node deploy and task queue sync—instead of opening HTTPS to Zulip from every Mac.
- Resolve the hostname from the webhook URL (Zulip Cloud looks like
*.zulipchat.com; self-hosted uses your domain). - Firewall or egress proxy: allow TCP 443 from the gateway subnet only to that hostname. Explicitly deny the same destination from builder subnets so misconfigured runners fail closed.
- TLS inspection: if a corporate proxy terminates TLS, import the same trust store OpenClaw uses and log only host plus HTTP status, never full URLs.
Screenshot placement: firewall rule row showing gateway VLAN → Zulip host 443 allow, builder VLAN → Zulip deny.
Optional: verify DNS and SNI from the gateway before go-live:
dig +short your-org.zulipchat.com
openssl s_client -connect your-org.zulipchat.com:443 -servername your-org.zulipchat.com </dev/null | head -n 5
Message template
Normalize before render. Each worker should emit: workflow, terminal state (succeeded, failed, cancelled), branch, short commit, run_url, mesh_node_id (hostname or pool tag), provider_run_id, and when failed a bounded failure_excerpt (roughly five to fifteen lines, no secrets).
Multi-node summary format (markdown example). Keep one message per terminal transition; use a compact header line so mobile clients scan quickly:
**CI** `ios-nightly` → **failed** · `main` @ `a1b2c3d`
**Node** `meshmac-pool-3` · **Run** [#1842](https://ci.example/runs/1842)
**Excerpt**
\`\`\`
error: no space left on device
\`\`\`
Map that block into the JSON field your Zulip integration expects—commonly content for markdown. Cap total size (stay well under a few tens of kilobytes) so you avoid 400 responses from oversized bodies. Align client timeouts with gateway rate limits and session concurrency.
Screenshot placement: Zulip message as rendered in web UI, showing stream, topic, and collapsed code block.
Failure retry and idempotency keys
Transient saturation yields 429 or 5xx. Use exponential backoff with full jitter, cap attempts, honor Retry-After when present, and serialize posts per stream when incidents spike—patterns match task queue retry steps and streaming backpressure.
Idempotency key. Before emit, compute idempotency_key = sha256(provider_run_id + ":" + state) (or equivalent) and skip if that key was successfully delivered inside your dedupe window—typically twenty-four to seventy-two hours for CI. That stops duplicate Zulip topics after flaky Wi-Fi or worker restarts.
| HTTP | Action |
|---|---|
400 |
Malformed payload—fix template against Zulip sample; no tight retry loop. |
401 / 403 |
Revoked or wrong key—rotate URL in Zulip, update secret file, probe. |
404 |
Deleted integration—regenerate URL, redeploy config. |
429 |
Throttle—lower concurrency, widen backoff, merge operator notices. |
5xx |
Upstream fault—bounded retries; prefer one merged incident card. |
Screenshot placement: gateway log tail showing single POST with http_code:200 and idempotency cache hit skipping a duplicate.
Merged health probe alerts
Synthetic probes (scheduled curl to Zulip), queue depth, and webhook HTTP error rates should not each spam their own topic during an outage. Emit one merged operator message when any combination crosses thresholds: include probe success or failure, last five delivery statuses summarized, and whether builders are still enqueueing events.
Reuse the same markdown template with a distinct prefix, for example **[ops]**, and route to a dedicated stream such as incidents so CI chatter stays readable. Cooldown the merge window (for example five to fifteen minutes) to align with merged retry alerting ideas in streaming gateway backpressure.
Minimal cron-style probe (run as the same service user; log exit code, not URL):
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
BODY=$(printf '%s' "{\"content\":\"**[ops probe]** ${TS}\"}")
/usr/bin/curl -fsS -m 15 -X POST "$ZULIP_INCOMING_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
--data-binary "$BODY" \
|| logger -t openclaw-zulip-probe "probe_failed"
During gateway failover drills, follow cluster permission and failover so two gateways never double-post the same webhook during cutover.
Checklist
- Gateway curl returns HTTP 200 and a visible Zulip message.
- Workers emit terminal transitions only; gateway owns dedupe.
- Firewall allows Zulip 443 from gateway only; builders denied direct egress.
- Rotation runbook lists owner, probe command, and revoke-old-URL step.
- Health and delivery failures merge under a cooldown window.
Summary
Stay gateway-first for OpenClaw → Zulip Webhook traffic on MeshMac, store one integration URL with tight POSIX modes, normalize multi-node fields for honest build notifications, bound retries with idempotency keys, and merge transport health with user-visible delivery. Browse the public homepage, blog index, and help center without signing in.
Add MeshMac Nodes Without Duplicating Zulip Secrets
Extra multi-node capacity should add throughput, not new webhook URLs on every Mac. Open public plans and the homepage to compare MeshMac rental tiers with no login, skim the blog for deployment checklists, and read help for SSH versus remote desktop access before checkout.