freezing-rocket-21802
12/19/2025, 2:46 AMtall-airline-16756
12/19/2025, 2:46 AMtall-airline-16756
12/19/2025, 2:46 AMroom_finished event and not for other webhook events.
Yes — you can filter webhooks so only room_finished is sent. Two ways:
• LiveKit Cloud Dashboard: Project → Settings → Webhooks. When adding/editing the webhook, uncheck all events except room_finished, then save. Only room_finished events will be sent to your Lambda.
• Self-hosted / config file: add event_filters in your server config:
webhook:
urls:
- <https://your-lambda-endpoint.com/webhook>
event_filters:
- room_finished
This sends only room_finished to that URL.
Note: Server SDK webhook receivers that accept all events will require you to filter in code; use the dashboard or config filtering to avoid extra Lambda invocations.
If you’d like, tell me whether you’re using LiveKit Cloud or a self-hosted instance and I’ll give exact Dashboard steps or a ready-to-use config snippet and a quick note on validating the webhook in Lambda.
Sources: Webhooks | LiveKit docs | server-sdk-js | livekit/livekit_composite | DeepWikifreezing-rocket-21802
12/19/2025, 2:48 AMtall-airline-16756
12/19/2025, 2:49 AMroom_finished.
Short answer: the server SDK webhook receiver will accept all LiveKit events; you cannot make the SDK itself pre-filter events before they arrive. Your Lambda must verify the request and then check the event type (exactly what your code already does).
What you’re doing correctly
• Verifying the JWT with TokenVerifier and comparing the SHA256 hash of the raw body.
• Parsing the raw body bytes and JSON only after verification.
• Branching on event_type == "room_finished" to run your cleanup logic and returning quickly for other events.
Optional improvements / best practices
• Return 200 quickly for non-matching events to minimize Lambda execution time (you already do this by only running DB/Twilio when room_finished).
• Keep the JWT verification and hash comparison as a fast gate before any DB/Twilio calls.
• If you expect a high volume of non-room events and want to avoid Lambda cold-starts or costs, consider:
◦ Using a lightweight proxy endpoint (e.g., small HTTP function) that verifies and filters, then forwards only room_finished to your heavier Lambda.
◦ If you self-host LiveKit, configure event_filters in your LiveKit server config so the webhook URL receives only room_finished (example snippet):
webhook:
urls:
- <https://your-api-gateway-url/webhook>
event_filters:
- room_finished
This prevents non-room events from being sent to the endpoint at the source.
If you’d like, tell me whether you want:
• a minimal proxy example to forward only room_finished to your Lambda, or
• an exact config.yaml snippet for self-hosted LiveKit, or
• a small optimization to reduce Lambda runtime (e.g., faster early return pattern). Sources: Webhooks | LiveKit docs | examples/survey | livekit/livekit_composite | DeepWiki | Server-side | livekit/livekit_composite | DeepWikifreezing-rocket-21802
12/19/2025, 2:52 AMtall-airline-16756
12/19/2025, 2:53 AMevent string and room object).
• Immediately return 200 for any event that is not room_finished to minimize work and cost.
• Keep logging useful debug info.
Below is a revised, minimal version of your handler with those changes. It preserves your verification steps and performs an early return for non-matching events.
import os
import json
import logging
import base64
import hashlib
import re
import psycopg
from dotenv import load_dotenv
from livekit import api as lk_api
from twilio.rest import Client as TwilioClient
load_dotenv()
DATABASE_URL = os.environ.get("DATABASE_URL")
LIVEKIT_API_KEY = os.environ.get("LIVEKIT_API_KEY")
LIVEKIT_API_SECRET = os.environ.get("LIVEKIT_API_SECRET")
TWILIO_ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = os.environ.get("TWILIO_AUTH_TOKEN")
TWILIO_FROM = os.environ.get("TWILIO_PHONE_NUMBER")
NOTIFY_NUMBERS = ['']
logger = logging.getLogger()
logger.setLevel(<http://logging.INFO|logging.INFO>)
try:
token_verifier = lk_api.TokenVerifier(LIVEKIT_API_KEY, LIVEKIT_API_SECRET)
except Exception as e:
logger.error(f"Failed to init LiveKit Verifier: {e}")
token_verifier = None
def lambda_handler(event, context):
try:
headers = event.get("headers", {}) or {}
# find Authorization header case-insensitively
auth_header = None
for k, v in headers.items():
if k.lower() == "authorization":
auth_header = v
break
if not auth_header:
logger.warning("Missing Authorization header")
return {"statusCode": 401, "body": "Missing Authorization"}
# raw body bytes for hash verification
if event.get("isBase64Encoded", False):
raw_body_bytes = base64.b64decode(event.get("body", "") or "")
else:
raw_body_bytes = (event.get("body") or "").encode("utf-8")
# local sha256 hex
local_hash = hashlib.sha256(raw_body_bytes).hexdigest()
if not token_verifier:
logger.error("Token verifier not initialized")
return {"statusCode": 500, "body": "Server Configuration Error"}
# Verify token and compare payload hash
try:
claims = token_verifier.verify(auth_header)
if getattr(claims, "sha256", None) != local_hash:
logger.error("Hash Mismatch")
return {"statusCode": 401, "body": "Hash Mismatch"}
except Exception as e:
logger.error(f"Token verification failed: {e}")
return {"statusCode": 401, "body": "Invalid Token"}
# parse JSON body
try:
body_json = json.loads(raw_body_bytes)
except Exception as e:
logger.error(f"Invalid JSON body: {e}")
return {"statusCode": 400, "body": "Invalid JSON"}
# Expected webhook shape: { "event": "room_finished", "room": { ... }, ... }
event_type = body_json.get("event")
if event_type != "room_finished":
# Early return for all other events
logger.debug(f"Ignoring event: {event_type}")
return {"statusCode": 200, "body": "ignored"}
# Handle room_finished
room = body_json.get("room", {}) or {}
room_name = room.get("name")
if not room_name:
logger.warning("room_finished payload missing room.name")
return {"statusCode": 200, "body": "no room name"}
<http://logger.info|logger.info>(f"Processing room_finished for {room_name}")
match = re.search(r"-(\d+)$", room_name)
if not match:
logger.warning(f"Could not parse Call ID from room name: {room_name}")
return {"statusCode": 200, "body": "no call id"}
call_id = int(match.group(1))
check_and_kill_zombie(call_id, room_name)
return {"statusCode": 200, "body": "ok"}
except Exception as e:
logger.error(f"Internal Server Error: {e}", exc_info=True)
return {"statusCode": 500, "body": "Internal Error"}
def check_and_kill_zombie(call_id, room_name):
try:
with psycopg.connect(DATABASE_URL) as conn:
with conn.cursor() as cur:
cur.execute("SELECT status, provider_id FROM call_sessions WHERE id = %s", (call_id,))
row = cur.fetchone()
if not row:
logger.warning(f"Call {call_id} not found")
return
current_status, provider_id = row
if current_status in ['in_progress', 'connecting']:
logger.error(f"ZOMBIE DETECTED: Call {call_id} stuck in '{current_status}'")
cur.execute("""
UPDATE call_sessions
SET status = 'failed',
ended_at = NOW(),
outcome = 'Technical Error',
updated_at = NOW()
WHERE id = %s
""", (call_id,))
conn.commit()
send_sms_alert(call_id, provider_id, current_status)
else:
<http://logger.info|logger.info>(f"Call {call_id} healthy (Status: {current_status})")
except Exception as e:
logger.error(f"DB Error for call {call_id}: {e}")
def send_sms_alert(call_id, provider_id, stuck_status):
if not (TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN and TWILIO_FROM):
logger.error("Cannot send SMS: credentials missing")
return
try:
client = TwilioClient(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
msg_body = (
f"Zombie Call Session:\n"
f"Call ID: {call_id}\n"
f"Provider: {provider_id}\n"
f"Was stuck in: '{stuck_status}'\n"
f"Action: Set to 'failed' via Webhook."
)
for number in NOTIFY_NUMBERS:
client.messages.create(body=msg_body, from_=TWILIO_FROM, to=number)
<http://logger.info|logger.info>(f"Sent Zombie SMS alerts for Call {call_id}")
except Exception as e:
logger.error(f"Failed to send Zombie SMS: {e}")
Notes:
• The LiveKit webhook uses top-level event and a room object; this code reads body_json["event"] and body_json["room"]["name"].
• Early return happens right after checking event_type != "room_finished", returning 200 quickly which minimizes processing and cost.
• If you want a lighter-weight pre-filter, I can provide a tiny proxy function that only forwards room_finished to this Lambda.freezing-rocket-21802
12/19/2025, 3:13 AMtall-airline-16756
12/19/2025, 3:13 AMevent field and returns early unless event == "room_finished".
• For room_finished, it reads body_json["room"]["name"] (handles missing keys safely).
• Uses the same regex you provided, r"-(\d+)$", to pull the trailing number from names like chronic-care-mgmt-2395 and converts it to an int for DB use.
So for a room named chronic-care-mgmt-2395 the handler will set call_id = 2395 and call check_and_kill_zombie(2395, "chronic-care-mgmt-2395") as before.
If you want, I can:
• Change the regex if your room naming varies, or
• Add extra validation/logging around the parsed call_id before DB updates. Which would you prefer?freezing-rocket-21802
12/19/2025, 3:23 AMtall-airline-16756
12/19/2025, 3:23 AMwebhook.urls in your config.yaml contains the exact API Gateway URL.
1. Confirm event filtering
• If Cloud: ensure room_finished is checked/enabled for that webhook (or leave all enabled while debugging).
• If self-hosted: confirm event_filters isn’t excluding room_finished.
1. Check LiveKit delivery logs (Cloud)
• In the Cloud dashboard Webhooks section you can view recent delivery attempts and errors. Look for requests to your API Gateway and any failure codes or error messages.
1. Check API Gateway + Lambda logs
• API Gateway: check CloudWatch request logs for the stage to see whether requests arrive and are rejected by API Gateway (e.g., 403, 404, 502).
• Lambda: check CloudWatch logs for your Lambda function for any incoming request logs. Your manual test shows it logs; look for entries with timestamps matching LiveKit events.
1. Validate the webhook request shape and headers
• LiveKit sends a JWT in the Authorization header and uses application/webhook+json. Your handler handles these; confirm API Gateway is forwarding the Authorization header to Lambda (APIGW sometimes drops headers if not configured).
◦ In API Gateway, ensure the Authorization header is included in your method’s mapping/templates or that you’re using a Lambda Proxy integration (which forwards headers automatically).
1. Confirm LiveKit can reach your API Gateway
• If your API Gateway requires an API key, IAM auth, custom authorizer, or is private (VPC endpoint), LiveKit will not reach it. Ensure the endpoint is publicly reachable and accepts simple POST with Authorization header.
1. Reproduce with a signed, LiveKit-like request
• You already built a correct test that signs the payload. One useful check: send that same signed test from an external host (not your dev machine behind firewall) to confirm API Gateway + Lambda behave the same from the public internet.
If you want, I can provide:
• A small script you can run to poll LiveKit Cloud webhook delivery logs (if you confirm you’re on LiveKit Cloud), or
• A step-by-step checklist for verifying API Gateway mapping and header forwarding, or
• A minimal curl example that simulates LiveKit’s request (signed JWT + content-type) for you to run publicly.
Tell me whether you are on LiveKit Cloud or self-hosted and which option above you want next and I’ll give the exact commands and checks.freezing-rocket-21802
12/19/2025, 3:26 AMtall-airline-16756
12/19/2025, 3:27 AMroom_finished event to your webhook URL.
Do you have the LiveKit CLI (lk) installed and authenticated to your Cloud project? If so, run one of these commands to create or update the webhook to only send `room_finished`:
• Create webhook:
lk webhook create \
--project p_3n4929oy90q \
--url <https://04iy6g08m9.execute-api.us-east-1.amazonaws.com/default/LivekitWebhookCallStateMgrLambda> \
--events room_finished \
--signed
• Update existing webhook (use the webhook id from lk webhook list):
lk webhook update WEBHOOK_ID \
--events room_finished
Notes:
• --signed ensures LiveKit signs the webhook with the project API key (your Lambda verifies this).
• After updating, LiveKit Cloud will only send room_finished to that URL, preventing other events from invoking your Lambda.
• The dashboard will still show the webhook URL but not the event list — use lk webhook list and lk webhook get WEBHOOK_ID to confirm the webhook’s events value.
If you want, I can provide:
• The exact lk webhook get output fields to check, or
• A command to list delivery attempts in the Cloud (if you want me to walk through checking delivery logs). Which would you like?freezing-rocket-21802
12/19/2025, 3:28 AMtall-airline-16756
12/19/2025, 3:29 AMlk CLI (v2.4.10) doesn’t show webhook commands, here’s a concise plan and actions to get your webhook delivering only room_finished and verify deliveries.
1) Confirm current Cloud webhook config
• Open Project → Settings → Webhooks and confirm the URL exactly matches your API Gateway endpoint (including path/stage).
• If the dashboard UI does not show event toggles, filtering via CLI isn’t available in your version.
2) Use Cloud delivery logs to see what LiveKit sent
• In the same Webhooks page look for Delivery / Recent Attempts. That view shows request timestamps, response codes, and error details for each delivery. Check for entries to your API Gateway URL and any failures.
3) Verify API Gateway is forwarding requests exactly
• Confirm the integration is Lambda Proxy (this forwards headers + body as-is).
• Ensure Authorization header is forwarded. If using a custom mapping template, include:
◦ headers.Authorization -> event.headers.Authorization
• Make sure the endpoint is public (no API Key/IAM restriction) so LiveKit Cloud can POST.
4) Quick tests to isolate where requests stop
• From a public host (or curl from your laptop) send a signed LiveKit-style request to the webhook URL using your test script — if that reaches Lambda but LiveKit deliveries do not, problem is on LiveKit side or delivery logs will show attempts/failures.
• If LiveKit delivery logs show 2xx but Lambda logs are empty, API Gateway might be returning success before invoking Lambda (check method integration and stage settings).
5) If dashboard logs show no attempts
• LiveKit isn’t sending. Common causes:
◦ Webhook disabled or misconfigured URL in project settings
◦ Project plan/feature limitations (contact Cloud UI or check project settings)
• If you see attempts with non-2xx responses, use the logged response body to debug (often Authorization missing or 4xx from API Gateway).
6) Next small checks I can provide commands for (pick one)
• I can give a curl/python snippet that simulates LiveKit-signed webhook exactly (so you can verify public reachability).
• I can list the exact CloudWatch filter queries to find API Gateway + Lambda logs for recent timestamps.
• I can show the API Gateway setting to ensure Authorization header is passed.
Summary: Your CLI v2.4.10 doesn’t include webhook event filtering. Use the Cloud Webhooks delivery logs to see what LiveKit is sending, confirm API Gateway is forwarding Authorization and body, and run a public signed test to compare. Tell me which of the three checks above you want the exact commands for (curl signed test / CloudWatch filter / API Gateway header check) and I’ll give step-by-step commands.
Sources: Webhooks | LiveKit docs | server-sdk-js | livekit/livekit_composite | DeepWikifreezing-rocket-21802
12/19/2025, 3:32 AMtall-airline-16756
12/19/2025, 3:32 AMevent_filters in that config.
When you are on LiveKit Cloud:
• Webhooks are managed by LiveKit Cloud. Dashboard or Cloud APIs determine delivery. You cannot change server config YAML for the LiveKit service because you’re not hosting it.
If you want next steps, I can:
• Walk you through verifying webhook delivery and API Gateway header forwarding for your Cloud webhook, or
• Show how to safely filter in your Lambda if dashboard filtering isn't available for your project.
Which would you like me to do?freezing-rocket-21802
12/19/2025, 4:15 AMtall-airline-16756
12/19/2025, 4:16 AMevent_filters in config.yaml.
◦ If dashboard filtering isn’t available for your project, filter in your code: verify the JWT, check body["event"], and return quickly for non-matching events (this is what your Lambda already does).
• Programmatic webhook management:
◦ LiveKit Cloud may expose REST endpoints / dashboard controls for webhook config. If your CLI/version lacks webhook commands, use the Cloud UI or Cloud API (if available for your plan) to create/update webhooks.
◦ When self-hosting, you edit server config YAML to add webhook.urls and event_filters.
Practical recommendations
• Keep your verification + early-return logic in the service that receives webhooks (ECS or Lambda). This is simplest and safe.
• If you need to avoid LiveKit invoking your endpoint for non-room_finished (to save Lambda/ECS cost), check if your Cloud project’s webhook settings expose event filtering. If not, consider self-hosting the LiveKit server or using a lightweight proxy that forwards only room_finished to your main handler.
• Ensure your public endpoint accepts LiveKit’s signed requests and that API Gateway/ALB forwards Authorization and body unchanged.
If you want, I can:
• Show the exact HTTP request shape LiveKit Cloud sends (headers, example body) so you can validate your receiver.
• Provide a tiny proxy example that accepts all webhooks, verifies them, and forwards only room_finished to your Lambda.
• Walk through how to check Cloud Webhooks delivery logs in the LiveKit dashboard and what to look for.
Which of those would help you most right now?freezing-rocket-21802
12/19/2025, 4:31 AMtall-airline-16756
12/19/2025, 4:31 AMapplication/webhook+json).
◦ A different LiveKit project key/secret is used to sign vs the one your Lambda verifies.
◦ JSON was re-serialized with different spacing/ordering before hashing on sender (LiveKit signs raw bytes it sends).
Immediate checks (do these first)
1. Confirm API Gateway is using Lambda Proxy integration so headers/body are forwarded unchanged.
2. Ensure API Gateway does not modify or re-encode the body:
◦ In API Gateway settings, add application/webhook+json to Binary Media Types (if using non-proxy or if body arrives base64).
◦ If using HTTP API, ensure payload passthrough is enabled.
1. Confirm the LiveKit project API key/secret in Lambda exactly matches the project that sent the webhook.
2. In LiveKit Cloud Webhooks delivery logs, inspect the request body shown (if available) and the response code/time.
Add diagnostic logs to reveal the mismatch
• Add logging of:
◦ Authorization header (the token string — only during debug; remove later)
◦ claims.sha256 from the verified token
◦ The local computed hash of the raw bytes (hex)
◦ Whether event body was base64-encoded coming into Lambda (event["isBase64Encoded"])
Minimal snippet to add around verification (log only while debugging):
# after computing raw_body_bytes and before comparing:
logger.debug(f"isBase64Encoded={event.get('isBase64Encoded')}")
logger.debug(f"local_hash={local_hash}")
# after token_verifier.verify(auth_header):
logger.debug(f"token_sha256={getattr(claims, 'sha256', None)}")
logger.debug(f"auth_header_preview={auth_header[:40]}...") # remove after debug
This will show whether the token claim and local hash differ and whether the body arrived base64-encoded.
Correct verification pattern to use (concise)
• Use the raw bytes delivered to Lambda exactly as received:
◦ If isBase64Encoded is True → base64 decode event["body"].
◦ Else → encode event["body"] to UTF-8 bytes (do not json.dumps or otherwise re-serialize).
• Compute sha256 on those exact bytes and compare to claims.sha256.
Example checklist to fix common issues
• If local_hash differs by small amount (like whitespace/newline):
◦ Check whether API Gateway or any middleware normalized the body. Use binary passthrough for application/webhook+json.
• If token_sha256 is missing or different:
◦ Confirm the Authorization token is the LiveKit-signed webhook token for this project.
◦ Confirm LiveKit is configured to sign webhooks for this webhook (dashboard shows “Signed with” — you already saw that).
• If Authorization header is missing in Lambda:
◦ Ensure API Gateway forwards the Authorization header (Lambda Proxy does this automatically).
• If LiveKit delivery logs show 4xx/5xx:
◦ Inspect logged response body — often API Gateway returns 4xx because of CORS, auth, or mapping errors.
If you want, I can:
• Provide an updated lambda_handler patch that adds the debug logs and preserves your secure checks (I can include it here).
• Walk you through the exact API Gateway setting to ensure application/webhook+json is passed through as raw bytes.
Which would you like me to give next — the small patched handler with debug logs, or the step-by-step API Gateway settings?