Skip to main content

Webhook Events

Dasha BlackBox sends webhook notifications when significant events occur during calls. Each webhook payload includes a type discriminator field that identifies the payload type, along with common fields and type-specific data. This reference documents all webhook payload types based on the actual API schema.

WebHookPayloadType Enum

All webhook payloads include a type field that serves as the discriminator. The possible values are:
Type ValueDescriptionStatus
StartWebHookPayloadCall is starting (pre-call webhook)Queued
CompletedWebHookPayloadCall completed successfullyCompleted
FailedWebHookPayloadCall failed with an errorFailed
CallDeadLineWebHookPayloadCall canceled due to deadline expirationCanceled
TransferWebHookPayloadTransfer decision requested (HTTP transfer)Running
ToolWebHookPayloadTool/function execution requestedRunning
UnknownWebHookPayloadUnknown or unhandled payload type-

Common Payload Fields

All webhook payloads inherit from BaseWebHookPayload and include these common fields:
FieldTypeRequiredDescription
typeWebHookPayloadTypeYesDiscriminator field identifying the payload type
statusCallStatusYesCurrent call status (Queued, Running, Completed, Failed, Canceled)
callIdstringYesUnique identifier for the call
agentIdstringYesID of the agent handling the call
orgIdstringYesOrganization identifier
callAdditionalDataobjectYesCustom metadata passed when creating the call
agentAdditionalDataobjectYesCustom metadata from agent configuration
serverJobIdstringNoInternal server job identifier
sipIncomingSipInfoNoSIP protocol information (if available)
endpointstringNoCall endpoint (phone number, SIP URI, etc.)

IncomingSipInfo Structure

When sip is present:
FieldTypeDescription
fromUserstringUser part of SIP From header
fromDomainstringDomain part of SIP From header
toUserstringUser part of SIP To header
toDomainstringDomain part of SIP To header
diversionstring[]Array of diversion headers

CallStatus Values

ValueDescription
UnknownUnknown status
CreatedCall created but not yet queued
PendingCall pending execution
QueuedCall queued and ready to start
RunningCall in progress
CompletedCall completed successfully
FailedCall failed with error
CanceledCall was canceled

CallConnectionType Values

ValueDescription
UnknownUnknown connection type
InboundAudioInbound phone call
OutboundAudioOutbound phone call
WebChatWeb-based chat session
WebCallWeb-based voice call
WebPhoneWeb phone connection

StartWebHookPayload

Sent when a call is about to start. This is a pre-call webhook that allows you to accept or reject the call, and optionally override the agent configuration. Status: Queued

Fields

FieldTypeRequiredDescription
type"StartWebHookPayload"YesDiscriminator
status"Queued"YesAlways Queued for start payloads
originalAgentConfigAgentConfigDtoYesThe current agent configuration at call start
(common fields)-YesAll fields from BaseWebHookPayload

Example Payload

{
  "type": "StartWebHookPayload",
  "status": "Queued",
  "callId": "660e8400-e29b-41d4-a716-446655440001",
  "agentId": "550e8400-e29b-41d4-a716-446655440000",
  "orgId": "org_123abc",
  "callAdditionalData": {
    "customerId": "cust_789",
    "campaignId": "fall-promo-2024"
  },
  "agentAdditionalData": {
    "department": "sales"
  },
  "endpoint": "+15551234567",
  "sip": {
    "fromUser": "+15551234567",
    "fromDomain": "sip.provider.com",
    "toUser": "agent-line",
    "toDomain": "blackbox.dasha.ai",
    "diversion": []
  },
  "originalAgentConfig": {
    "primaryLanguage": "en-US",
    "llmConfig": {
      "vendor": "openai",
      "model": "gpt-4.1-mini",
      "prompt": "You are a helpful customer support agent..."
    },
    "ttsConfig": {
      "vendor": "Dasha",
      "voiceId": "default-voice"
    },
    "sttConfig": {
      "vendor": "Auto"
    }
  }
}

Start Webhook Response

Your webhook must return one of two response types:

StartWebHookResponseAccept

Return this to accept the call and optionally override the agent configuration.
FieldTypeRequiredDescription
accepttrueYesMust be true to accept
overrideAgentConfigAgentConfigDtoNoComplete agent configuration to use instead of the original. Note: This replaces the entire configuration, not a partial merge.
additionalDataobjectNoAdditional data to inject into the call context
{
  "accept": true,
  "overrideAgentConfig": {
    "primaryLanguage": "es-ES",
    "llmConfig": {
      "vendor": "openai",
      "model": "gpt-4.1",
      "prompt": "Eres un agente de soporte al cliente...",
      "options": { "temperature": 0.7 }
    },
    "ttsConfig": {
      "vendor": "ElevenLabs",
      "voiceId": "spanish-voice-id"
    },
    "sttConfig": {
      "vendor": "Auto"
    }
  },
  "additionalData": {
    "customerTier": "premium",
    "preferredLanguage": "es"
  }
}

StartWebHookResponseReject

Return this to reject the call.
FieldTypeRequiredDescription
acceptfalseYesMust be false to reject
reasonMessagestringYesReason for rejection (minimum 1 character)
{
  "accept": false,
  "reasonMessage": "Customer account is suspended"
}

CompletedWebHookPayload

Sent when a call completes successfully. Contains the full transcription and call results. Status: Completed

Fields

FieldTypeRequiredDescription
type"CompletedWebHookPayload"YesDiscriminator
status"Completed"YesAlways Completed
callTypeCallConnectionTypeYesType of call connection
resultCompletedOutputYesCall result data including post-call analysis
transcriptionTranscription[]YesComplete conversation transcript
inspectorUrlstringYesURL to view call in Call Inspector
recordingUrlstringNoURL to call recording (if enabled)
createdTimestring (ISO 8601)YesWhen the call was created
completedTimestring (ISO 8601)YesWhen the call completed
durationSecondsnumberYesTotal call duration in seconds
(common fields)-YesAll fields from BaseWebHookPayload

CompletedOutput Structure

FieldTypeDescription
failDetailsstringFailure details (if any)
finishReasonstringReason for call completion
serviceStatusstringService status code
statusstringFinal status
postCallAnalysisobjectResults from configured post-call analysis labels
transferInformationTransferInfo[]Array of transfer information (duration, etc.)

Transcription Structure

FieldTypeDescription
speakerstringSpeaker identifier ("user" or "assistant")
namestringSpeaker name (optional)
textstringTranscribed text
startTimestring (ISO 8601)When this segment started
endTimestring (ISO 8601)When this segment ended

Example Payload

{
  "type": "CompletedWebHookPayload",
  "status": "Completed",
  "callId": "660e8400-e29b-41d4-a716-446655440001",
  "agentId": "550e8400-e29b-41d4-a716-446655440000",
  "orgId": "org_123abc",
  "callAdditionalData": {
    "customerId": "cust_789"
  },
  "agentAdditionalData": {},
  "endpoint": "+15551234567",
  "callType": "OutboundAudio",
  "createdTime": "2024-10-20T14:32:00.000Z",
  "completedTime": "2024-10-20T14:37:45.789Z",
  "durationSeconds": 345.789,
  "inspectorUrl": "https://blackbox.dasha.ai/calls/660e8400-e29b-41d4-a716-446655440001",
  "recordingUrl": "https://storage.blackbox.dasha.ai/recordings/660e8400-e29b-41d4-a716-446655440001.mp3",
  "result": {
    "finishReason": "user_hangup",
    "status": "success",
    "postCallAnalysis": {
      "sentiment": "positive",
      "issueResolved": true,
      "callCategory": "billing_inquiry"
    },
    "transferInformation": []
  },
  "transcription": [
    {
      "speaker": "assistant",
      "name": "Agent",
      "text": "Hello! Thank you for calling. How can I help you today?",
      "startTime": "2024-10-20T14:32:15.000Z",
      "endTime": "2024-10-20T14:32:19.500Z"
    },
    {
      "speaker": "user",
      "name": "Customer",
      "text": "Hi, I have a question about my recent bill.",
      "startTime": "2024-10-20T14:32:20.000Z",
      "endTime": "2024-10-20T14:32:24.000Z"
    },
    {
      "speaker": "assistant",
      "name": "Agent",
      "text": "Of course! I'd be happy to help with your billing question. Could you tell me more about what you're seeing?",
      "startTime": "2024-10-20T14:32:24.500Z",
      "endTime": "2024-10-20T14:32:30.000Z"
    }
  ]
}

FailedWebHookPayload

Sent when a call fails due to an error. Status: Failed

Fields

FieldTypeRequiredDescription
type"FailedWebHookPayload"YesDiscriminator
status"Failed"YesAlways Failed
callTypeCallConnectionTypeYesType of call connection
errorMessagestringYesDescription of the error
inspectorUrlstringNoURL to view call in Call Inspector
recordingUrlstringNoURL to partial recording (if available)
createdTimestring (ISO 8601)YesWhen the call was created
completedTimestring (ISO 8601)YesWhen the call failed
(common fields)-YesAll fields from BaseWebHookPayload

Example Payload

{
  "type": "FailedWebHookPayload",
  "status": "Failed",
  "callId": "660e8400-e29b-41d4-a716-446655440002",
  "agentId": "550e8400-e29b-41d4-a716-446655440000",
  "orgId": "org_123abc",
  "callAdditionalData": {
    "customerId": "cust_790"
  },
  "agentAdditionalData": {},
  "endpoint": "+15551234568",
  "callType": "OutboundAudio",
  "createdTime": "2024-10-20T14:40:00.000Z",
  "completedTime": "2024-10-20T14:40:30.456Z",
  "errorMessage": "Call was not answered within timeout period",
  "inspectorUrl": "https://blackbox.dasha.ai/calls/660e8400-e29b-41d4-a716-446655440002"
}

CallDeadLineWebHookPayload

Sent when a call is canceled because its scheduled deadline expired before the call could be executed. Status: Canceled

Fields

FieldTypeRequiredDescription
type"CallDeadLineWebHookPayload"YesDiscriminator
status"Canceled"YesAlways Canceled
callTypeCallConnectionTypeYesType of call connection
reasonMessagestringNoReason for cancellation
createdTimestring (ISO 8601)NoWhen the call was created
completedTimestring (ISO 8601)NoWhen the call was canceled
(common fields)-YesAll fields from BaseWebHookPayload

Example Payload

{
  "type": "CallDeadLineWebHookPayload",
  "status": "Canceled",
  "callId": "660e8400-e29b-41d4-a716-446655440003",
  "agentId": "550e8400-e29b-41d4-a716-446655440000",
  "orgId": "org_123abc",
  "callAdditionalData": {
    "scheduledFor": "2024-10-20T14:00:00.000Z"
  },
  "agentAdditionalData": {},
  "endpoint": "+15551234569",
  "callType": "OutboundAudio",
  "createdTime": "2024-10-20T13:00:00.000Z",
  "completedTime": "2024-10-20T15:00:00.000Z",
  "reasonMessage": "Call deadline expired before execution"
}

TransferWebHookPayload

Sent during HTTP transfers to request a transfer decision. Your webhook determines where to route the call. Status: Running

Fields

FieldTypeRequiredDescription
type"TransferWebHookPayload"YesDiscriminator
status"Running"YesAlways Running
transferReasonstringNoReason for transfer execution
transcriptionTranscription[]YesPartial transcription up to the transfer moment
(common fields)-YesAll fields from BaseWebHookPayload

Example Payload

{
  "type": "TransferWebHookPayload",
  "status": "Running",
  "callId": "660e8400-e29b-41d4-a716-446655440004",
  "agentId": "550e8400-e29b-41d4-a716-446655440000",
  "orgId": "org_123abc",
  "callAdditionalData": {
    "customerId": "cust_791",
    "accountTier": "premium"
  },
  "agentAdditionalData": {},
  "endpoint": "+15551234570",
  "transferReason": "Customer requested human agent for billing dispute",
  "transcription": [
    {
      "speaker": "assistant",
      "name": "Agent",
      "text": "I understand you'd like to speak with a billing specialist about your dispute.",
      "startTime": "2024-10-20T14:45:00.000Z",
      "endTime": "2024-10-20T14:45:05.000Z"
    },
    {
      "speaker": "user",
      "name": "Customer",
      "text": "Yes, I need to talk to someone who can actually help me with this refund.",
      "startTime": "2024-10-20T14:45:06.000Z",
      "endTime": "2024-10-20T14:45:10.000Z"
    }
  ]
}

Transfer Webhook Response

Your webhook should return a transfer decision specifying where to route the call. The response format depends on your transfer configuration.
For complete details on transfer webhook responses and HTTP transfer configuration, see Call Transfers. Transfer parameters documented there align with this TransferWebHookPayload.
Example Response (Cold Transfer):
{
  "transferTo": "+15559876543",
  "transferType": "cold"
}
Example Response (Warm Transfer):
{
  "transferTo": "+15559876543",
  "transferType": "warm",
  "briefing": "Customer Jane Doe needs help with billing dispute. Account: premium tier."
}

ToolWebHookPayload

Sent when the AI agent invokes a configured tool/function during a conversation. Status: Running

Fields

FieldTypeRequiredDescription
type"ToolWebHookPayload"YesDiscriminator
status"Running"YesAlways Running
toolNamestringYesName of the tool being called
argumentsobjectYesArguments passed to the tool (as defined in tool schema)
(common fields)-YesAll fields from BaseWebHookPayload

Example Payload

{
  "type": "ToolWebHookPayload",
  "status": "Running",
  "callId": "660e8400-e29b-41d4-a716-446655440005",
  "agentId": "550e8400-e29b-41d4-a716-446655440000",
  "orgId": "org_123abc",
  "callAdditionalData": {
    "customerId": "cust_792"
  },
  "agentAdditionalData": {},
  "endpoint": "+15551234571",
  "toolName": "check_account_balance",
  "arguments": {
    "customerId": "cust_792",
    "accountType": "checking"
  }
}

Tool Webhook Response

Return the tool execution result as JSON. The structure depends on your tool definition.
{
  "balance": 1234.56,
  "currency": "USD",
  "lastTransaction": "2024-10-19T10:15:00Z"
}

Event Handling Best Practices

Use the Type Discriminator

Always route based on the type field, not assumptions about payload structure:
const handlers = {
  'StartWebHookPayload': handleStart,
  'CompletedWebHookPayload': handleCompleted,
  'FailedWebHookPayload': handleFailed,
  'CallDeadLineWebHookPayload': handleDeadline,
  'TransferWebHookPayload': handleTransfer,
  'ToolWebHookPayload': handleTool
};

app.post('/webhooks/blackbox', (req, res) => {
  const handler = handlers[req.body.type];
  if (handler) {
    return handler(req.body, res);
  }
  console.warn(`Unknown webhook type: ${req.body.type}`);
  res.status(200).send('OK');
});

Respond Quickly

Dasha BlackBox expects webhook responses within the configured timeout (default: 10 seconds for start webhooks, 5 seconds for tools).
// For long-running operations, acknowledge immediately and process asynchronously
app.post('/webhooks/blackbox', async (req, res) => {
  if (req.body.type === 'CompletedWebHookPayload') {
    // Acknowledge immediately
    res.status(200).send('OK');
    // Process asynchronously
    processCompletedCall(req.body).catch(console.error);
    return;
  }
  // For start/transfer/tool webhooks, must respond with data
  // ...
});

Handle All Payload Types

Ensure your webhook handles all possible payload types gracefully:
app.post('/webhooks/blackbox', (req, res) => {
  const { type } = req.body;

  // Log all received webhooks for debugging
  console.log(`Received webhook: ${type}, callId: ${req.body.callId}`);

  switch (type) {
    case 'StartWebHookPayload':
      return handleStart(req, res);
    case 'CompletedWebHookPayload':
    case 'FailedWebHookPayload':
    case 'CallDeadLineWebHookPayload':
      // Status webhooks don't require response data
      processStatusWebhook(req.body);
      return res.status(200).send('OK');
    case 'TransferWebHookPayload':
      return handleTransfer(req, res);
    case 'ToolWebHookPayload':
      return handleTool(req, res);
    default:
      console.warn(`Unhandled webhook type: ${type}`);
      return res.status(200).send('OK');
  }
});

Complete Webhook Handler Example

Here’s a production-ready Express.js webhook handler implementing all payload types:
const express = require('express');
const app = express();
app.use(express.json());

// Start webhook handler - accept/reject calls
async function handleStartWebhook(payload, res) {
  const { callId, callAdditionalData, originalAgentConfig } = payload;

  try {
    // Example: Check if customer is allowed to call
    const customerId = callAdditionalData?.customerId;
    const isAllowed = await checkCustomerStatus(customerId);

    if (!isAllowed) {
      return res.json({
        accept: false,
        reasonMessage: 'Customer account is not active'
      });
    }

    // Example: Override config based on customer tier
    const customerTier = await getCustomerTier(customerId);
    if (customerTier === 'premium') {
      return res.json({
        accept: true,
        overrideAgentConfig: {
          ...originalAgentConfig,
          llmConfig: {
            ...originalAgentConfig.llmConfig,
            model: 'gpt-4.1' // Upgrade to better model for premium
          }
        },
        additionalData: {
          customerTier: 'premium'
        }
      });
    }

    // Accept with default config
    return res.json({ accept: true });

  } catch (error) {
    console.error('Start webhook error:', error);
    // On error, accept with default config
    return res.json({ accept: true });
  }
}

// Completed webhook handler - process results
async function handleCompletedWebhook(payload) {
  const {
    callId,
    transcription,
    result,
    durationSeconds,
    callAdditionalData
  } = payload;

  // Store call data
  await storeCallRecord({
    callId,
    customerId: callAdditionalData?.customerId,
    duration: durationSeconds,
    transcript: transcription,
    postCallAnalysis: result.postCallAnalysis,
    finishReason: result.finishReason
  });

  // Trigger follow-up actions based on analysis
  if (result.postCallAnalysis?.issueResolved === false) {
    await createFollowUpTask(callId, callAdditionalData?.customerId);
  }
}

// Failed webhook handler - handle errors
async function handleFailedWebhook(payload) {
  const { callId, errorMessage, callAdditionalData } = payload;

  await logCallFailure({
    callId,
    customerId: callAdditionalData?.customerId,
    error: errorMessage
  });

  // Alert on repeated failures
  const recentFailures = await getRecentFailureCount(
    callAdditionalData?.customerId,
    '1h'
  );
  if (recentFailures > 3) {
    await alertOperations('High failure rate for customer');
  }
}

// Transfer webhook handler - route calls
async function handleTransferWebhook(payload, res) {
  const { callAdditionalData, transferReason } = payload;

  try {
    // Route based on customer tier or transfer reason
    const customerId = callAdditionalData?.customerId;
    const accountTier = callAdditionalData?.accountTier;

    if (accountTier === 'premium') {
      // Premium customers get warm transfer to dedicated rep
      const assignedRep = await getAssignedRep(customerId);
      return res.json({
        transferTo: assignedRep.phone,
        transferType: 'warm',
        briefing: `Premium customer ${customerId}. Reason: ${transferReason}`
      });
    }

    // Standard customers get cold transfer to queue
    return res.json({
      transferTo: '+1-555-0100',
      transferType: 'cold'
    });

  } catch (error) {
    console.error('Transfer webhook error:', error);
    // Fallback to default support line
    return res.json({
      transferTo: '+1-555-0100',
      transferType: 'cold'
    });
  }
}

// Tool webhook handler - execute functions
async function handleToolWebhook(payload, res) {
  const { toolName, arguments: args, callAdditionalData } = payload;

  try {
    switch (toolName) {
      case 'check_account_balance':
        const balance = await getAccountBalance(
          args.customerId,
          args.accountType
        );
        return res.json(balance);

      case 'schedule_callback':
        const scheduled = await scheduleCallback(
          callAdditionalData?.customerId,
          args.preferredTime
        );
        return res.json({ success: true, scheduledTime: scheduled });

      case 'process_refund':
        const refund = await processRefund(
          args.orderId,
          args.amount
        );
        return res.json(refund);

      default:
        return res.json({ error: `Unknown tool: ${toolName}` });
    }
  } catch (error) {
    console.error(`Tool ${toolName} error:`, error);
    return res.json({ error: error.message });
  }
}

// Main webhook endpoint
app.post('/webhooks/blackbox', async (req, res) => {
  const payload = req.body;

  console.log(`Webhook received: ${payload.type}, callId: ${payload.callId}`);

  switch (payload.type) {
    case 'StartWebHookPayload':
      return handleStartWebhook(payload, res);

    case 'CompletedWebHookPayload':
      await handleCompletedWebhook(payload);
      return res.status(200).send('OK');

    case 'FailedWebHookPayload':
      await handleFailedWebhook(payload);
      return res.status(200).send('OK');

    case 'CallDeadLineWebHookPayload':
      console.log(`Call ${payload.callId} canceled: ${payload.reasonMessage}`);
      return res.status(200).send('OK');

    case 'TransferWebHookPayload':
      return handleTransferWebhook(payload, res);

    case 'ToolWebHookPayload':
      return handleToolWebhook(payload, res);

    default:
      console.warn(`Unknown webhook type: ${payload.type}`);
      return res.status(200).send('OK');
  }
});

// Health check
app.get('/health', (req, res) => res.status(200).send('OK'));

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Webhook server running on port ${PORT}`);
});

Next Steps