Add API test suite for Bird Voice Webhook endpoints, including comprehensive lifecycle tests for inbound call handling scenarios. Extend fixtures with department gate creation support and update OpenAPI spec validations.
This commit is contained in:
+289
-92
@@ -901,16 +901,22 @@ paths:
|
||||
summary: Process inbound Bird voice call lifecycle
|
||||
operationId: birdInboundVoiceCallWebhook
|
||||
description: >
|
||||
Stateful inbound-call webhook that answers the call immediately, runs a DTMF-driven IVR for
|
||||
phone-controlled entrance and exit gates, and enforces a timeout hangup flow. During the
|
||||
first 300 seconds from call start, DTMF input is extracted from payload fields such as
|
||||
`dtmf`, `digit`, `digits`, `keys`, `result.keys`, and nested `conditions[].value`. Values
|
||||
like `1#` are normalized to a single menu digit before the route resolves the department
|
||||
selection and then the gate type selection (`1=entrance`, `2=exit`). When a valid gate is
|
||||
resolved, the webhook triggers the corresponding `department_gates` phone-call gate, announces
|
||||
the result, sends a hangup command, and keeps the completed state cached until Bird reports a
|
||||
terminal call status. At or after 300 seconds, the webhook says `timeout reached`, waits 10
|
||||
seconds, sends a hangup command once, and polls Bird call status until terminal.
|
||||
Stateful inbound-call webhook that owns department and gate selection for phone-controlled
|
||||
gates. The preferred Bird Flow Builder integration is the native-flow mode:
|
||||
send top-level `callId`, `channelId`, and `workspaceId` to fetch IVR prompt data,
|
||||
then submit the selected DTMF digits as `keys` in a follow-up request. In this mode
|
||||
the webhook returns a plain `200 OK` JSON body with `prompt`, `stage`, and `gather`
|
||||
settings that Bird native voice steps can consume directly. For backward compatibility,
|
||||
the webhook also supports the older `{ payload, request, waitConditions }` contract and
|
||||
returns a raw `202 Accepted` Bird `callCommand` gather envelope that resumes on
|
||||
`call_command_gather_finished`. The Bird Flow itself must answer the inbound call before
|
||||
invoking this HTTP step; the backend answer attempt is only a best-effort fallback.
|
||||
Department options are generated from `department_gates` records with `config.type=PHONE_CALL`,
|
||||
ordered by `departments.order_priority`, and support multi-digit DTMF selections such as `10#`.
|
||||
After a department is chosen, the webhook returns compact gate options, for example `Press 1 for exit`
|
||||
when exit is the only available phone-controlled gate for that department. When a valid gate is confirmed,
|
||||
the webhook opens the gate through the corresponding `department_gates` phone-call record and returns
|
||||
a `200 OK` completion result.
|
||||
parameters:
|
||||
- in: query
|
||||
name: workspaceId
|
||||
@@ -940,11 +946,25 @@ paths:
|
||||
$ref: '#/components/schemas/BirdInboundCallWebhookRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Lifecycle phase result for this webhook invocation
|
||||
description: Native-flow gather data or gate action result for this webhook invocation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BirdInboundCallWebhookResponse'
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/BirdInboundCallWebhookFlowGatherResponse'
|
||||
- $ref: '#/components/schemas/BirdInboundCallWebhookActionResultResponse'
|
||||
'202':
|
||||
description: Gather command accepted and returned to Bird
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BirdInboundCallWebhookGatherAcceptedResponse'
|
||||
'400':
|
||||
description: Malformed Bird webhook payload
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BirdInboundCallWebhookTransportErrorResponse'
|
||||
|
||||
# Bird Numbers
|
||||
/bird/numbers:
|
||||
@@ -16460,122 +16480,299 @@ components:
|
||||
channelId:
|
||||
type: string
|
||||
format: uuid
|
||||
status:
|
||||
type: string
|
||||
description: Optional inbound call status from webhook payload
|
||||
example: ongoing
|
||||
payload:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
endKey:
|
||||
type: string
|
||||
example: "#"
|
||||
retries:
|
||||
type: integer
|
||||
example: 3
|
||||
timeout:
|
||||
type: integer
|
||||
example: 30
|
||||
say:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
locale:
|
||||
type: string
|
||||
example: "en-US"
|
||||
voice:
|
||||
type: string
|
||||
example: "female"
|
||||
request:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
callId:
|
||||
type: string
|
||||
example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09"
|
||||
workspaceId:
|
||||
type: string
|
||||
format: uuid
|
||||
channelId:
|
||||
type: string
|
||||
format: uuid
|
||||
waitConditions:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
timeout:
|
||||
type: string
|
||||
example: "PT10M"
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
action:
|
||||
type: string
|
||||
example: "continue"
|
||||
name:
|
||||
type: string
|
||||
example: "call_command_gather_finished"
|
||||
event:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
result:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
resumeData:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
dtmf:
|
||||
type: string
|
||||
description: DTMF value when present, for example `1` or `1#`
|
||||
example: "5"
|
||||
description: DTMF value when present, for example `1`, `1#`, or `10#`
|
||||
example: "10#"
|
||||
digit:
|
||||
type: string
|
||||
description: Alternate DTMF field, also accepts values such as `1#`
|
||||
example: "5"
|
||||
description: Alternate DTMF field, also accepts values such as `10#`
|
||||
example: "10#"
|
||||
digits:
|
||||
type: string
|
||||
description: Alternate DTMF field
|
||||
example: "5"
|
||||
example: "10#"
|
||||
keys:
|
||||
type: string
|
||||
description: Alternate DTMF field returned by gather results
|
||||
example: "1#"
|
||||
call:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
data:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
event:
|
||||
example: "10#"
|
||||
key:
|
||||
type: string
|
||||
example: "1"
|
||||
input:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: object
|
||||
additionalProperties: true
|
||||
conditions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
BirdInboundCallWebhookResponse:
|
||||
type: object
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/BirdInboundCallWebhookFlowGatherResponse'
|
||||
- $ref: '#/components/schemas/BirdInboundCallWebhookActionResultResponse'
|
||||
- $ref: '#/components/schemas/BirdInboundCallWebhookGatherAcceptedResponse'
|
||||
- $ref: '#/components/schemas/BirdInboundCallWebhookTransportErrorResponse'
|
||||
|
||||
BirdInboundCallWebhookFlowGatherResponse:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
requestId:
|
||||
type: string
|
||||
example: "request-123"
|
||||
callId:
|
||||
type: string
|
||||
example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09"
|
||||
status:
|
||||
type: string
|
||||
enum: [gather]
|
||||
completed:
|
||||
type: boolean
|
||||
example: true
|
||||
data:
|
||||
enum: [false]
|
||||
stage:
|
||||
type: string
|
||||
enum: [department_select, gate_type_select]
|
||||
prompt:
|
||||
type: string
|
||||
gather:
|
||||
type: object
|
||||
properties:
|
||||
phase:
|
||||
input:
|
||||
type: string
|
||||
enum: [lock_not_acquired, input_window, timeout_window, completed, terminal_completion]
|
||||
stage:
|
||||
enum: [dtmf]
|
||||
maxNumKeys:
|
||||
type: integer
|
||||
endKey:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [department_select, gate_type_select, completed]
|
||||
call_id:
|
||||
type: string
|
||||
example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09"
|
||||
completed:
|
||||
type: boolean
|
||||
elapsed_seconds:
|
||||
example: "#"
|
||||
timeout:
|
||||
type: integer
|
||||
retries:
|
||||
type: integer
|
||||
say:
|
||||
type: object
|
||||
properties:
|
||||
locale:
|
||||
type: string
|
||||
example: "en-US"
|
||||
voice:
|
||||
type: string
|
||||
example: "female"
|
||||
text:
|
||||
type: string
|
||||
selection:
|
||||
type: object
|
||||
properties:
|
||||
departmentId:
|
||||
type: integer
|
||||
nullable: true
|
||||
timeout_seconds:
|
||||
type: integer
|
||||
nullable: true
|
||||
input_received:
|
||||
type: boolean
|
||||
nullable: true
|
||||
input_changed:
|
||||
type: boolean
|
||||
nullable: true
|
||||
last_input:
|
||||
departmentName:
|
||||
type: string
|
||||
nullable: true
|
||||
selected_department_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
selected_gate_type:
|
||||
gateType:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [entrance, exit]
|
||||
gate_id:
|
||||
gateId:
|
||||
type: integer
|
||||
nullable: true
|
||||
gate_opened:
|
||||
invalidSelectionCount:
|
||||
type: integer
|
||||
resumed:
|
||||
type: boolean
|
||||
statusCode:
|
||||
type: integer
|
||||
enum: [200]
|
||||
statusText:
|
||||
type: string
|
||||
enum: [OK]
|
||||
|
||||
BirdInboundCallWebhookActionResultResponse:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
example: "request-123"
|
||||
result:
|
||||
type: object
|
||||
properties:
|
||||
callId:
|
||||
type: string
|
||||
example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09"
|
||||
status:
|
||||
type: string
|
||||
enum: [completed, failed, ignored]
|
||||
action:
|
||||
type: string
|
||||
enum: [gate_opened, gate_open_failed, no_action, ignored]
|
||||
message:
|
||||
type: string
|
||||
departmentId:
|
||||
type: integer
|
||||
nullable: true
|
||||
gateType:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [entrance, exit]
|
||||
gateId:
|
||||
type: integer
|
||||
nullable: true
|
||||
gateOpened:
|
||||
type: boolean
|
||||
nullable: true
|
||||
answered_at:
|
||||
type: integer
|
||||
nullable: true
|
||||
timeout_announced_at:
|
||||
type: integer
|
||||
nullable: true
|
||||
hangup_sent_at:
|
||||
type: integer
|
||||
nullable: true
|
||||
poll_attempts:
|
||||
type: integer
|
||||
nullable: true
|
||||
poll_error:
|
||||
resumeData:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
action:
|
||||
type: string
|
||||
nullable: true
|
||||
terminal_status:
|
||||
example: "continue"
|
||||
completed:
|
||||
type: boolean
|
||||
example: true
|
||||
result:
|
||||
type: string
|
||||
nullable: true
|
||||
reason:
|
||||
example: "gate_opened"
|
||||
gateOpened:
|
||||
type: boolean
|
||||
example: true
|
||||
completedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
statusCode:
|
||||
type: integer
|
||||
enum: [200]
|
||||
statusText:
|
||||
type: string
|
||||
enum: [OK]
|
||||
|
||||
BirdInboundCallWebhookGatherAcceptedResponse:
|
||||
type: object
|
||||
properties:
|
||||
event:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
requestId:
|
||||
type: string
|
||||
example: "request-123"
|
||||
result:
|
||||
type: object
|
||||
properties:
|
||||
callId:
|
||||
type: string
|
||||
example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09"
|
||||
command:
|
||||
type: string
|
||||
enum: [gather]
|
||||
id:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [accepted]
|
||||
resumeData:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
action:
|
||||
type: string
|
||||
example: "continue"
|
||||
statusCode:
|
||||
type: integer
|
||||
enum: [202]
|
||||
statusText:
|
||||
type: string
|
||||
enum: [Accepted]
|
||||
suspendedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
resumedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
|
||||
BirdInboundCallWebhookTransportErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
statusCode:
|
||||
type: integer
|
||||
enum: [400, 500]
|
||||
statusText:
|
||||
type: string
|
||||
enum: [Bad Request, Internal Server Error]
|
||||
error:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
nullable: true
|
||||
meta:
|
||||
oneOf:
|
||||
- type: array
|
||||
items: {}
|
||||
- type: object
|
||||
additionalProperties: true
|
||||
example: []
|
||||
includes:
|
||||
oneOf:
|
||||
- type: array
|
||||
items: {}
|
||||
- type: object
|
||||
additionalProperties: true
|
||||
example: []
|
||||
|
||||
BirdVoiceCallCreateRequest:
|
||||
type: object
|
||||
|
||||
Reference in New Issue
Block a user