-
Couldn't load subscription status.
- Fork 109
Description
LiveKit Webhook SHA256 Checksum Verification Fails on AWS ALB (Works Locally)
Problem Description
LiveKit webhooks work perfectly when testing locally but fail with sha256 checksum of body does not match error when deployed behind AWS Application Load Balancer (ALB). The issue appears to be that the ALB is modifying/parsing the webhook request body before it reaches our Express.js service, breaking the cryptographic signature verification. ( I'm just forwarding it to my target group which should not break this flow, I just want to know if this can the issue ?? if yes what is the solution ) .
Environment
- Local: Works perfectly ✅
- AWS: Fails with ALB in front of ECS service ❌
- Express.js: 4.x with TypeScript
- LiveKit: Using
livekit-server-sdkWebhookReceiver
Error Logs
Local (Working):
AWS ALB (Failing):
ARN
2025-07-28T16:23:16.149Z
�[32m[2025-07-28 16:23:16.149] INFO: N/A : REQUEST: 580214 POST /api/livekit/webhook �[39m
Link
2025-07-28T16:23:16.149Z
LiveKit webhook received: {
Link
2025-07-28T16:23:16.149Z
contentType: 'application/webhook+json',
Link
2025-07-28T16:23:16.149Z
contentLength: '399',
Link
2025-07-28T16:23:16.149Z
userAgent: 'Go-http-client/2.0',
Link
2025-07-28T16:23:16.149Z
hasAuthHeader: true,
Link
2025-07-28T16:23:16.149Z
authHeaderLength: 235,
Link
2025-07-28T16:23:16.149Z
bodyType: 'object',
Link
2025-07-28T16:23:16.149Z
bodyLength: 2,
Link
2025-07-28T16:23:16.149Z
isBuffer: false,
Link
2025-07-28T16:23:16.149Z
bodyPreview: {}
Link
2025-07-28T16:23:16.149Z
}
Link
2025-07-28T16:23:16.149Z
�[32m[2025-07-28 16:23:16.149] INFO: N/A : LiveKit webhook raw request received (15 bytes)�[39m
Link
2025-07-28T16:23:16.150Z
�[31m[2025-07-28 16:23:16.150] ERROR: N/A : Error processing LiveKit webhook: sha256 checksum of body does not match�[39m
Link
2025-07-28T16:23:16.150Z
�[32m[2025-07-28 16:23:16.150] INFO: N/A : RESPONSE: 580214 POST /api/livekit/webhook | Status: 400 | Time: 1.
Current Code
Express App Setup (app.ts)
const app: expressWs.Application = expressWs(express()).app;
// URL rewriting middleware FIRST
app.use((req, res, next) => {
req.url = req.url.replace(/.*\/api/, "/api").replace(/.*\/ws/, "");
next();
});
// Configure raw body parsing for LiveKit webhooks FIRST (before JSON parser)
app.use('/api/livekit/webhook', express.raw({ type: 'application/webhook+json' }));
// Body parser middleware
app.use(express.urlencoded({ extended: true }));
app.use(express.json({ limit: "10mb" }));
// CORS middleware
app.use(cors(options));Webhook Handler (livekit.ts)
export const handleLivekitWebhook = async (req: Request, res: Response) => {
try {
console.log('LiveKit webhook received:', {
contentType: req.get('content-type'),
contentLength: req.get('content-length'),
userAgent: req.get('user-agent'),
hasAuthHeader: !!req.get('authorization'),
bodyType: typeof req.body,
bodyLength: req.body ? (Buffer.isBuffer(req.body) ? req.body.length : JSON.stringify(req.body).length) : 0,
isBuffer: Buffer.isBuffer(req.body),
bodyPreview: Buffer.isBuffer(req.body) ? req.body.toString('utf8', 0, 100) : req.body
});
// Convert to string for webhook verification
const rawBodyString = Buffer.isBuffer(req.body) ? req.body.toString('utf8') : JSON.stringify(req.body);
// This fails on AWS with "sha256 checksum of body does not match"
const event = await livekitWebhookReceiver.receive(
rawBodyString,
req.get('Authorization')
);
// ... handle event
} catch (error: any) {
winstonLogger.error(`Error processing LiveKit webhook: ${error.message}`, error);
res.status(400).send('Invalid webhook payload');
}
};AWS ALB Configuration
Current ALB Listener Rules:
Priority 3: Path = /participant/* → userology-dev-participant-tg
ALB Settings:
- Protocol: HTTPS:443
- Target Group: HTTP backend on port 8080
- Health Check: Default settings
What We've Tried
- Different Express middleware orders
- Various
express.raw()configurations - Different content-type specifications
- Custom middleware to capture raw body
Questions
-
Is this a known ALB behavior? Does ALB parse/transform JSON webhook payloads by default?
-
Should we configure ALB differently? Are there specific target group attributes or listener settings that preserve raw request bodies?
-
Alternative solutions?
Expected Behavior
The webhook handler should receive the exact raw bytes that LiveKit sent, allowing proper SHA256 signature verification to work on AWS just like it does locally.
Any insights on ALB behavior with webhook payloads or suggestions for preserving raw request bodies would be greatly appreciated!
Additional Context:
- Other API endpoints work fine through the same ALB
- Only webhook signature verification is affected
- LiveKit sends
Content-Type: application/webhook+json - The Authorization header with signature arrives intact