mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat: add Exotel webhook controller and service for call event parsing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
24
src/exotel/exotel.controller.ts
Normal file
24
src/exotel/exotel.controller.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Controller, Post, Body, Logger, HttpCode } from '@nestjs/common';
|
||||
import { ExotelService } from './exotel.service';
|
||||
import type { ExotelWebhookPayload } from './exotel.types';
|
||||
|
||||
@Controller('webhooks/exotel')
|
||||
export class ExotelController {
|
||||
private readonly logger = new Logger(ExotelController.name);
|
||||
|
||||
constructor(private readonly exotelService: ExotelService) {}
|
||||
|
||||
@Post('call-status')
|
||||
@HttpCode(200)
|
||||
async handleCallStatus(@Body() payload: ExotelWebhookPayload) {
|
||||
this.logger.log(`Received Exotel webhook: ${payload.event_details?.event_type}`);
|
||||
|
||||
const callEvent = this.exotelService.parseWebhook(payload);
|
||||
|
||||
// TODO: Forward to CallEventsService (Task 4)
|
||||
// For now, just log
|
||||
this.logger.log(`Call event: ${JSON.stringify(callEvent)}`);
|
||||
|
||||
return { status: 'received' };
|
||||
}
|
||||
}
|
||||
10
src/exotel/exotel.module.ts
Normal file
10
src/exotel/exotel.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ExotelController } from './exotel.controller';
|
||||
import { ExotelService } from './exotel.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ExotelController],
|
||||
providers: [ExotelService],
|
||||
exports: [ExotelService],
|
||||
})
|
||||
export class ExotelModule {}
|
||||
31
src/exotel/exotel.service.ts
Normal file
31
src/exotel/exotel.service.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import type { ExotelWebhookPayload, CallEvent } from './exotel.types';
|
||||
|
||||
@Injectable()
|
||||
export class ExotelService {
|
||||
private readonly logger = new Logger(ExotelService.name);
|
||||
|
||||
parseWebhook(payload: ExotelWebhookPayload): CallEvent {
|
||||
const { event_details, call_details } = payload;
|
||||
|
||||
const eventType = event_details.event_type === 'answered' ? 'answered'
|
||||
: event_details.event_type === 'terminal' ? 'ended'
|
||||
: 'ringing';
|
||||
|
||||
const callEvent: CallEvent = {
|
||||
exotelCallSid: call_details.call_sid,
|
||||
eventType,
|
||||
direction: call_details.direction,
|
||||
callerPhone: call_details.customer_details?.number ?? '',
|
||||
agentName: call_details.assigned_agent_details?.name ?? 'Unknown',
|
||||
agentPhone: call_details.assigned_agent_details?.number ?? '',
|
||||
duration: call_details.total_talk_time,
|
||||
recordingUrl: call_details.recordings?.[0]?.url,
|
||||
callStatus: call_details.call_status,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
this.logger.log(`Parsed Exotel event: ${eventType} for call ${call_details.call_sid}`);
|
||||
return callEvent;
|
||||
}
|
||||
}
|
||||
35
src/exotel/exotel.types.ts
Normal file
35
src/exotel/exotel.types.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// Exotel webhook payload (from their API docs)
|
||||
export type ExotelWebhookPayload = {
|
||||
event_details: {
|
||||
event_type: 'answered' | 'terminal';
|
||||
};
|
||||
call_details: {
|
||||
call_sid: string;
|
||||
direction: 'inbound' | 'outbound';
|
||||
call_status?: string;
|
||||
total_talk_time?: number;
|
||||
assigned_agent_details?: {
|
||||
name: string;
|
||||
number: string;
|
||||
};
|
||||
customer_details?: {
|
||||
number: string;
|
||||
name?: string;
|
||||
};
|
||||
recordings?: { url: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
// Internal call event (normalized)
|
||||
export type CallEvent = {
|
||||
exotelCallSid: string;
|
||||
eventType: 'ringing' | 'answered' | 'ended';
|
||||
direction: 'inbound' | 'outbound';
|
||||
callerPhone: string;
|
||||
agentName: string;
|
||||
agentPhone: string;
|
||||
duration?: number;
|
||||
recordingUrl?: string;
|
||||
callStatus?: string;
|
||||
timestamp: string;
|
||||
};
|
||||
Reference in New Issue
Block a user