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 { data, errors } = req.body;

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

if (hasErrors) {
console.error(`callId=${data.callId} has ${errors.length} error(s)`);
for (const err of errors) {
console.error(` - [${err.code}] ${err.message}`);
}
} else {
console.log(`callId=${data.callId} status=${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()

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

return {"ok": True}

Slack​

Registering a Slack Incoming Webhook URL automatically posts the formatted notification message 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:
- [CALL_CONNECTION_FAILED] Failed to initiate or connect the call.
━━━━━━━━━━━━━━
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.