shy-raincoat-67106
07/22/2025, 11:47 AMimport { WebhookReceiver, AgentDispatchClient } from 'livekit-server-sdk';
import { FastifyPluginAsync } from 'fastify';
const receiver = new WebhookReceiver(
process.env.LIVEKIT_API_KEY!,
process.env.LIVEKIT_API_SECRET!
);
export const livekitWebhookHandler: FastifyPluginAsync = async (fastify) => {
<http://fastify.post|fastify.post>('/api/livekit-webhook', {
config: { rawBody: true },
}, async (request, reply) => {
// Verify webhook signature
try {
const event = await receiver.receive(
(request.raw as any).rawBody as string,
request.headers['lk-signature'] as string || ''
);
if (event.event === 'participant_joined' && event.participant?.attributes?.sip) {
const sipAttrs = event.participant.attributes.sip;
const roomName = event.room?.name || '';
const phoneNumber = (typeof sipAttrs === 'object' && sipAttrs !== null && 'phoneNumber' in sipAttrs)
? String(sipAttrs.phoneNumber)
: 'unknown';
const dialedNumber = (typeof sipAttrs === 'object' && sipAttrs !== null && 'trunkPhoneNumber' in sipAttrs)
? String(sipAttrs.trunkPhoneNumber)
: 'unknown';
// Create dispatch client
const dispatchClient = new AgentDispatchClient(
process.env.LIVEKIT_URL!,
process.env.LIVEKIT_API_KEY!,
process.env.LIVEKIT_API_SECRET!
);
// Dispatch agent with metadata
await dispatchClient.createDispatch(
roomName,
process.env.LIVEKIT_AGENT_NAME!,
{
metadata: JSON.stringify({
isInbound: true,
roomName,
sipIdentity: event.participant.identity,
callerPhoneNumber: phoneNumber,
dialedNumber: dialedNumber,
// Add any additional context needed by the agent
timestamp: new Date().toISOString(),
source: 'sip'
})
}
);
<http://fastify.log.info|fastify.log.info>(`Dispatched agent to room ${roomName} for call from ${phoneNumber}`);
}
return reply.status(200).send({ success: true });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
fastify.log.error('Error processing webhook:', error);
return reply.status(401).send({
error: 'Invalid webhook signature or processing error',
details: errorMessage
});
}
});
};tall-airline-16756
07/22/2025, 11:47 AMtall-airline-16756
07/22/2025, 11:49 AMAgentDispatchClient, should use the standard Bearer token format:
Sources: Webhooks | LiveKit Docsshy-raincoat-67106
07/22/2025, 11:49 AMtall-airline-16756
07/22/2025, 11:51 AMAuthorization: Bearer <jwt_token>
LiveKit then does a quick check to make sure everything looks good - kind of like a friendly bouncer making sure you're on the guest list. If everything checks out, you're all set!
Just remember, this token gets created in your backend using your API key and secret, and it's your golden ticket for all authenticated LiveKit API calls.
Sources: src/lib/controller.ts | packages/livekit-server-sdk/src/ServiceBase.ts | livekit-api/livekit/api/_service.py | Authentication Examples | livekit/livekit_composite | DeepWiki | auth.go