Test Cisco Webex webhooks locally
Receive Cisco Webex webhook events on localhost with a Horizon tunnel, create the webhook through the API, and verify X-Spark-Signature.
Receive Webex events on your laptop while you build, with a URL Webex can reach.
Webex has no dashboard page for webhooks. You create them with the Webex API.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - A Webex access token. To receive messages, the token needs the
spark:messages_readscope.
Start your app
When you give a webhook a secret, Webex sends an HMAC of the raw JSON body in the X-Spark-Signature header. The algorithm is HMAC SHA1, and the digest is hex. Read the body with request.text() before you parse it.
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(request: Request) {
const secret = process.env.WEBEX_WEBHOOK_SECRET;
if (!secret) {
return new Response("Missing WEBEX_WEBHOOK_SECRET", { status: 500 });
}
const body = await request.text();
const received = request.headers.get("x-spark-signature") ?? "";
const expected = createHmac("sha1", secret).update(body).digest("hex");
const receivedBuffer = Buffer.from(received);
const expectedBuffer = Buffer.from(expected);
const isValid =
receivedBuffer.length === expectedBuffer.length &&
timingSafeEqual(receivedBuffer, expectedBuffer);
if (!isValid) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(body);
console.log(`Received Webex event: ${event.resource} ${event.event}`);
return new Response("ok", { status: 200 });
}Pick a random secret and store it in an environment variable.
WEBEX_WEBHOOK_SECRET=replace-with-a-long-random-stringStart the app on port 3000.
npm run devStart a tunnel
Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so your webhook would point at a dead URL after a restart. Reserve it first on the Subdomains page. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:3000 -s my-webexHorizon prints HORIZON: Tunnel connected. Your public URL is https://my-webex.hrzn.run. Keep this terminal open.
Create the webhook with the API
Send POST https://webexapis.com/v1/webhooks with your token as a Bearer token. name, targetUrl, resource and event are required. secret is optional, but the handler above needs it. filter narrows the events, for example to one room.
curl -X POST https://webexapis.com/v1/webhooks \
-H "Authorization: Bearer $WEBEX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Horizon test",
"targetUrl": "https://my-webex.hrzn.run/api/webhooks/webex",
"resource": "messages",
"event": "created",
"secret": "replace-with-a-long-random-string"
}'Use the same value for secret as for WEBEX_WEBHOOK_SECRET.
Trigger an event
Webex's docs describe no resend for webhook deliveries. Trigger a real event instead. With the webhook above, post a message in a space the token's owner is in.
Check it works
Post a message. The Horizon terminal prints one line:
POST 200 /api/webhooks/webexYour app terminal prints:
Received Webex event: messages createdTroubleshooting
The signature doesn't match
- Check that
WEBEX_WEBHOOK_SECRETis identical to thesecretyou sent to the API. - Compute the HMAC over the raw body. Don't run
JSON.parseandJSON.stringifyfirst. - Use SHA1 with a hex digest.
- Restart
npm run devafter you edit.env.local.
The webhook has no signature header
You created the webhook without secret. Create it again with a secret.
The API rejects the request
The token needs a read scope for the resource. For messages, that is spark:messages_read.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-webex and create the webhook again with the new targetUrl. -s needs a subdomain you reserved, see Pricing.
Next steps
- Read Webex's blog post Using a webhook secret.
- Read the Webex webhooks guide.
Test Twilio SMS webhooks locally
Receive Twilio SMS webhooks on localhost with a Horizon tunnel, verify X-Twilio-Signature, and reply with TwiML.
Test Zoom webhooks locally
Receive Zoom webhook events on localhost with a Horizon tunnel, answer the endpoint URL validation challenge, and verify x-zm-signature.