Skip to main content

Sample Receivers

Custom: Node.js (Express)

Minimal example with Bearer authentication. Error detection uses the presence of errors.

// Node.js (Express) + Bearer authentication
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
// 1. Verify the Bearer token
const auth = req.headers.authorization ?? '';
if (auth !== `Bearer ${process.env.RECHO_WEBHOOK_TOKEN}`) {
return res.status(401).send('unauthorized');
}

const { eventType, body } = req.body;

// 2. Detect errors via body.errors, NOT via body.data.callStatus
const hasErrors = Array.isArray(body.errors) && body.errors.length > 0;

if (hasErrors) {
console.error(`[${eventType}] callId=${body.data.callId} has ${body.errors.length} error(s)`);
for (const err of body.errors) {
console.error(` - [${err.code}] ${err.message} (phase: ${err.phase})`);
}
} else {
console.log(`[${eventType}] callId=${body.data.callId} status=${body.data.callStatus}`);
}

res.status(200).send('ok');
});

app.listen(3000);

Custom: Python (FastAPI)

Minimal example with RSA signature verification, including timestamp checking to mitigate replay attacks.

# Python (FastAPI) + RSA signature verification
import base64
import time
from fastapi import FastAPI, HTTPException, Request
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

app = FastAPI()

with open("recho_public_key.pem", "rb") as f:
public_key = serialization.load_pem_public_key(f.read())

@app.post("/webhook")
async def receive(request: Request):
raw_body = await request.body()
signature_b64 = request.headers.get("x-recho-signature", "")
timestamp = request.headers.get("x-recho-timestamp", "")

# 1. Validate timestamp (within 5 minutes)
if not timestamp or abs(time.time() - int(timestamp)) > 300:
raise HTTPException(401, "stale timestamp")

# 2. Verify signature
signed_payload = f"{timestamp}.{raw_body.decode()}".encode()
try:
public_key.verify(
base64.b64decode(signature_b64),
signed_payload,
padding.PKCS1v15(),
hashes.SHA256(),
)
except Exception:
raise HTTPException(401, "invalid signature")

payload = await request.json()
body = payload["body"]

# 3. Error detection
if body.get("errors"):
for err in body["errors"]:
print(f"[{err['code']}] {err['message']}")

return {"ok": True}

Slack

Registering a Slack Incoming Webhook URL automatically posts the formatted text content to the channel. No receiver implementation needed.

Example posted content for an outbound-call error:

[Recho] Outbound call error
━━━━━━━━━━━━━━
callId: abc-123
callStatus: CALLING
callSid: CA94b51...
clientId: client-1
━━━━━━━━━━━━━━
Errors:
- [NETWORK_ERROR] Connection timed out (phase: CONNECTING)
━━━━━━━━━━━━━━
Time: 2026-05-12 10:24:00

Microsoft Teams

Like Slack, a registered Workflows / Incoming Webhook URL receives the formatted message automatically. No receiver implementation needed.

For Teams, the payload is converted internally into an Adaptive Card format.

Email

For email endpoints, the subject and text are forwarded as-is to the registered email address.

The destination email address is set at registration time; delivery uses Recho's internal email infrastructure.