Build an integration bridge
A bridge is a mirror between a ScaiWave room and a channel/room on another system. ScaiWave handles ScaiWave→foreign relay; you handle the foreign→ScaiWave webhook.
1. Plan the mapping#
Decide:
- Which direction: inbound only, outbound only, or both?
- Which ScaiWave room ↔ which foreign channel?
- How are foreign user identities represented? (Most platforms have a user id; you'll use it to dedupe shadow participants.)
- What's the HMAC scheme on the foreign side? (We'll use that for both directions.)
2. Create the bridge#
1 2 3 4 5 6 7 8 9 10 | |
Response includes bridge_id and shared_secret (the HMAC key).
Store the secret somewhere safe — it's shown once. Both sides
will sign with it.
3. Configure the foreign side#
The foreign side needs to know:
- The shared secret (for signing incoming webhooks to ScaiWave and verifying outgoing ones).
- The ScaiWave inbound URL:
<host>/v1/bridges/<bridge_id>/inbound.
When the foreign side has a new message, POST to that URL with:
1 2 | |
The signature is HMAC-SHA256(shared_secret, f"{ts}.{body}") —
the same Stripe-style scheme.
Body:
{
"event_id": "<unique-id-from-foreign-side>",
"sender": {
"external_id": "U12345",
"display_name": "Alice Anderson",
"avatar_url": "https://crm.acme.com/avatar/U12345.png"
},
"content": {
"msgtype": "swp.text",
"body": "Hello from Acme CRM"
},
"thread_id": null,
"reply_to_external_id": null
}
event_id is for idempotency — ScaiWave dedupes within a 10-minute
window. sender.external_id is what you map to a shadow
participant on the ScaiWave side; ScaiWave creates one
automatically if needed.
4. ScaiWave creates the event#
When the inbound webhook arrives:
- ScaiWave verifies the HMAC. Mismatch → 401, logged.
- Looks up or creates a
bridgeshadow participant forsender.external_id. - Creates an event in the bridge's room with
sender_id = shadow.id. The original sender info is embedded inevent.content._imported_sender. - The event flows out over WebSocket like any other.
5. Outbound (ScaiWave → foreign)#
Every event in the bridged room is routed through the
relay_message_to_bridges ARQ worker. It:
- Looks up the bridge config.
- Builds a JSON payload (similar shape to inbound).
- Signs with the same shared secret.
- POSTs to
bridge.outbound_webhook. - Retries with exponential backoff on 5xx.
Your endpoint receives:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Verify the HMAC the same way; if it doesn't match, return 401. Otherwise post into your channel.
6. Slack / Discord / Teams (built-in transports)#
These don't need a custom webhook endpoint on your side; ScaiWave's relay knows their APIs.
For Slack:
1 2 3 4 5 6 7 8 9 | |
ScaiWave subscribes to Slack's Events API at
/v1/bridges/<id>/slack/events and relays outbound via
chat.postMessage. The Slack signing secret lives on your side and
in ScaiWave; the bot token is needed for the outbound surface.
7. Operational#
- Pause a bridge:
POST /v1/admin/bridges/{id}/pause. Useful during incident response or maintenance. - Rotate the secret:
POST /v1/admin/bridges/{id}/rotate-secret. The old secret continues to work for 5 minutes for in-flight messages. - Delete:
DELETE /v1/admin/bridges/{id}. The bridge's history of shadow participants and events remains; future flow stops.
8. Error handling#
- HMAC mismatch (inbound) → 401, logged for admin review.
- Foreign side rate-limited (429) → exponential backoff,
retries 3 times. After that the message is marked
delivery_failedin the audit log. - Foreign side unreachable → retries up to 1 hour, then alert the admin.
Where to go next#
- API: Bridges.
- Concepts: Bridges vs. Federation.