TetraFi provides two channels for real-time data: WebSocket for low-latency streaming (preferred for solvers and active takers) and webhooks for reliable server-to-server notifications.
60-second event stream test. Install wscat (npm install -g wscat), connect, and authenticate - you'll see the auth-success acknowledgment within a second of running this:
1wscat -c wss://api.tetrafi.io/v1/ws2> {"type":"auth","token":"sk_test_..."}3< {"type":"auth.ok","environment":"sandbox"}4> {"type":"subscribe","channels":["rfq.*","settlement.*"]}5< {"type":"subscribe.ok","channels":["rfq.*","settlement.*"]}Once subscribed, every matching event will print to stdout in real time.
Use WebSocket when latency matters (sub-second updates) and your client is long-lived. Use webhooks when you need durable server-to-server delivery and your endpoint is publicly addressable.
| WebSocket | Webhooks | |
|---|---|---|
| Latency | Sub-second | 1-5 seconds typical |
| Reliability | Best-effort (reconnect on drop) | At-least-once delivery with retries |
| Best for | Active takers, solvers, dashboards | Backend systems, accounting pipelines |
| Client requirements | Persistent connection, long-lived process | Public HTTPS endpoint |
| Setup complexity | Connect + auth + subscribe | Register endpoint + verify signatures |
| Replay support | Last 100 events via cursor | All events stored 7 days, replayable |
| Backpressure handling | Server drops events on slow client | Server retries with backoff |
Most production integrations use both: WebSocket for the live UI / quote auction loop, and webhooks as a reliable fallback for settlement events that absolutely cannot be missed (e.g. accounting reconciliation).
Connect to the real-time event stream for sub-second quote and settlement updates:
- Open WebSocket to
wss://api.tetrafi.io/v1/ws - Send authentication message with your API key
- Subscribe to one or more event channels
- Receive events as JSON messages
- Respond to server
pingframes withpongevery 30 seconds
1const ws = new WebSocket("wss://api.tetrafi.io/v1/ws");23ws.onopen = () => {4 // Step 1: Authenticate5 ws.send(JSON.stringify({6 type: "auth",7 token: process.env.TETRAFI_API_KEY,8 }));910 // Step 2: Subscribe to channels11 ws.send(JSON.stringify({12 type: "subscribe",13 channels: ["rfq.*", "settlement.*"],14 // Optional: filter by corridor15 corridors: ["ethereum-optimism"],16 }));17};1819ws.onmessage = (e) => {20 const event = JSON.parse(e.data);21 switch (event.type) {22 case "rfq.created": handleRFQ(event.rfq); break;23 case "quote.received": handleQuote(event.quote); break;24 case "settlement.complete": handleSettlement(event.settlement); break;25 case "ping": ws.send(JSON.stringify({ type: "pong" })); break;26 }27};2829// Reconnect with exponential backoff30let retryCount = 0;31ws.onclose = () => {32 const delay = Math.min(30000, 1000 * Math.pow(2, retryCount++));33 setTimeout(connect, delay);34};Subscribe using exact event types or glob patterns:
| Pattern | Matches |
|---|---|
rfq.created | New RFQ submitted |
quote.received | Solver submitted quote |
rfq.* | All RFQ events |
settlement.* | All settlement events |
audit.recorded | WORM entry created |
Implement WebSocket reconnection with exponential backoff. Production connections may drop during deployments or network interruptions. The server sends a ping every 30 seconds - you must respond with pong to stay connected.
TetraFi emits 7 event types covering the complete settlement lifecycle. The typical event sequence for a successful trade follows this order:
11. rfq.created ← Taker submits intent22. quote.received (×N) ← Solvers respond (typically 3-7 quotes)33. quote.accepted ← Taker selects winning quote44. settlement.pending ← Escrow locked, settlement in flight55. settlement.complete ← Both legs settled atomically66. audit.recorded ← WORM entry written78Failure path:93. quote.accepted104. settlement.pending115. settlement.failed ← Refund issued, both legs reverted126. audit.recorded ← Failure recorded in WORM trailIdempotency. Each event has a unique id (evt_*). Your handler must be idempotent - the same event may be delivered more than once during retries or reconnects. Store the evt_* IDs you've already processed and skip duplicates.
Emitted when a taker submits a new Request for Quote.
1{2 "type": "rfq.created",3 "id": "evt_abc123",4 "timestamp": "2024-03-10T12:00:00.000Z",5 "rfq": {6 "id": "rfq_xyz789",7 "pair": "USDC/USDT",8 "side": "buy",9 "amount": "1000000.00",10 "corridor": "ethereum-optimism",11 "deadline": 1710000005,12 "complianceAttestation": "0x..."13 }14}Emitted when a solver submits a quote (visible to the taker only).
1{2 "type": "quote.received",3 "id": "evt_def456",4 "timestamp": "2024-03-10T12:00:01.500Z",5 "quote": {6 "id": "qt_ghi012",7 "rfqId": "rfq_xyz789",8 "price": "0.9995",9 "solverId": "solver_alpha",10 "ttl": 30,11 "confidence": 0.9812 }13}Emitted when the taker accepts a quote, triggering escrow creation.
1{2 "type": "quote.accepted",3 "quote": { "id": "qt_ghi012", "price": "0.9995" },4 "settlement": { "id": "stl_jkl345", "status": "pending" }5}Emitted when funds are locked in the origin chain escrow.
1{2 "type": "settlement.pending",3 "settlement": {4 "id": "stl_jkl345",5 "escrowTxHash": "0x7f3a...",6 "originChainId": 1,7 "destinationChainId": 10,8 "fillDeadline": 17100000359 }10}Emitted when DvP settlement succeeds - both legs complete atomically.
1{2 "type": "settlement.complete",3 "settlement": {4 "id": "stl_jkl345",5 "originTxHash": "0x7f3a...",6 "destinationTxHash": "0x9b2d...",7 "amount": "1000000.00",8 "settledAt": "2024-03-10T12:00:42.000Z"9 }10}Emitted when settlement times out or fails. Escrow auto-refunds the taker.
1{2 "type": "settlement.failed",3 "settlement": {4 "id": "stl_jkl345",5 "error": "fill_timeout",6 "refundTxHash": "0xc4f1...",7 "refundedAt": "2024-03-10T12:01:15.000Z"8 }9}Emitted when a WORM evidence entry is written to the ledger.
1{2 "type": "audit.recorded",3 "audit": {4 "tradeId": "stl_jkl345",5 "type": "settlement_complete",6 "timestamp": "2024-03-10T12:00:42.100Z",7 "hash": "0xab12...",8 "prevHash": "0xcd34..."9 }10}Webhooks deliver events to your server endpoint via HTTPS POST. Use webhooks for reliable, persistent notifications that don't require an active connection.
1const webhook = await tetrafi.webhooks.create({2 url: "https://your-server.com/webhook/tetrafi",3 events: ["settlement.*", "audit.recorded"],4 secret: generateSecureSecret(), // Store this securely5});67console.log("Webhook ID:", webhook.id);1// List webhooks2const webhooks = await tetrafi.webhooks.list();34// Update5await tetrafi.webhooks.update(webhook.id, {6 events: ["settlement.complete"],7});89// Delete10await tetrafi.webhooks.delete(webhook.id);1112// Send test event13await tetrafi.webhooks.test(webhook.id);Every webhook POST includes signature headers for security verification. Always verify before processing:
1import crypto from "crypto";23function verifyWebhook(4 body: string,5 signature: string,6 timestamp: string,7 secret: string8): boolean {9 // Prevent replay attacks: reject requests older than 5 minutes10 const age = Date.now() / 1000 - parseInt(timestamp);11 if (age > 300) return false;1213 // Compute expected signature14 const expected = crypto15 .createHmac("sha256", secret)16 .update(`{timestamp}.{body}`)17 .digest("hex");1819 // Timing-safe comparison20 return crypto.timingSafeEqual(21 Buffer.from(signature),22 Buffer.from(expected)23 );24}2526// Express handler27app.post("/webhook/tetrafi", express.raw({ type: "*/*" }), (req, res) => {28 const signature = req.headers["x-tetrafi-signature"] as string;29 const timestamp = req.headers["x-tetrafi-timestamp"] as string;30 const body = req.body.toString();3132 if (!verifyWebhook(body, signature, timestamp, WEBHOOK_SECRET)) {33 return res.status(401).json({ error: "Invalid signature" });34 }3536 const event = JSON.parse(body);37 handleEvent(event);38 res.status(200).json({ received: true });39});| Header | Description |
|---|---|
X-TetraFi-Signature | HMAC-SHA256 signature of timestamp.body |
X-TetraFi-Timestamp | Unix timestamp when the webhook was sent |
X-TetraFi-Event | Event type (e.g. settlement.complete) |
X-TetraFi-WebhookId | Your webhook endpoint ID |
X-TetraFi-DeliveryId | Unique delivery attempt ID |
TetraFi retries failed webhook deliveries with exponential backoff:
| Attempt | Delay | Cumulative Wait |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | 5s | 5s |
| 3 | 30s | 35s |
| 4 | 5min | 5m 35s |
| 5 | 1hr | 1h 5m 35s |
A delivery is considered failed if your endpoint returns a non-2xx status code or times out (30s timeout).
A webhook endpoint is automatically disabled after 5 consecutive delivery failures. Re-enable via the dashboard or POST /v1/webhooks/{id}/enable. You'll receive an email notification when a webhook is disabled.
Webhook deliveries may occasionally be duplicated due to retry logic. Use the X-TetraFi-DeliveryId header to deduplicate:
1// Store processed delivery IDs to prevent double-processing2const processedIds = new Set<string>();34app.post("/webhook/tetrafi", (req, res) => {5 const deliveryId = req.headers["x-tetrafi-delivery-id"] as string;67 if (processedIds.has(deliveryId)) {8 return res.status(200).json({ received: true, duplicate: true });9 }1011 processedIds.add(deliveryId);12 // Process event...13 res.status(200).json({ received: true });14});For production, store processed delivery IDs in a database with a TTL of 48 hours. An in-memory Set works for development but won't survive process restarts.