| title | Callbacks and DTMF |
|---|---|
| description | Voice provider callbacks, statuses and keypad actions |
IncidentRelay can receive provider callbacks for:
- call status changes;
- DTMF phone keypad input;
- provider errors.
When IncidentRelay creates a call, it passes callback_url to the provider.
Example:
https://incidentrelay.example.com/api/integrations/voice/callback/12/change-me-channel-secret
The URL format is:
/api/integrations/voice/callback/{channel_id}/{secret}
The provider should send call events to the callback URL.
IncidentRelay will:
1. Validate callback secret.
2. Load the globally configured voice provider.
3. Pass raw callback payload to provider.parse_callback().
4. Find notification by provider call_id.
5. Store callback status and payload.
6. Write callback event history.
7. Apply DTMF action if configured.
Example provider callback:
{
"call_id": "abc-123",
"event_type": "status",
"status": "answered"
}Provider normalization:
return [
VoiceCallCallbackEvent(
call_id=payload["call_id"],
event_type="status",
status=payload["status"],
raw=payload,
)
]Recommended statuses:
queued
ringing
answered
completed
failed
busy
no_answer
cancelled
unknown
IncidentRelay stores the latest status in the notification record and also stores callback history.
DTMF means phone keypad input.
Default mapping:
{
"1": "acknowledge",
"2": "resolve"
}Example spoken message:
IncidentRelay alert 123.
Disk is full.
Severity critical.
Press 1 to acknowledge.
Press 2 to resolve.
If the user presses 1, provider sends:
{
"call_id": "abc-123",
"event_type": "dtmf",
"digit": "1"
}IncidentRelay maps it to:
acknowledge
If the user presses 2, IncidentRelay maps it to:
resolve
A provider may also send the normalized action directly:
{
"call_id": "abc-123",
"event_type": "dtmf",
"action": "acknowledge"
}In this case IncidentRelay does not need to map the digit.
Providers may send error events.
{
"call_id": "abc-123",
"event_type": "error",
"status": "failed",
"message": "Insufficient balance"
}Provider normalization:
return [
VoiceCallCallbackEvent(
call_id=payload["call_id"],
event_type="error",
status="failed",
message=payload.get("message"),
raw=payload,
)
]Some providers sign webhook callbacks.
parse_callback() receives:
payload
headers
raw_body
query_argsUse headers and raw_body to validate provider signatures.
Example:
def parse_callback(self, payload, headers=None, raw_body=None, query_args=None):
signature = headers.get("X-Provider-Signature") if headers else None
if not self._is_valid_signature(raw_body or b"", signature):
raise RuntimeError("invalid provider callback signature")
...IncidentRelay validates its own callback secret before calling parse_callback().
Provider-specific signature validation is optional but recommended when the provider supports it.
Some providers do not support callbacks.
In this case, the provider may implement:
def get_call_status(self, call_id: str) -> VoiceCallResult:
...Example:
def get_call_status(self, call_id: str) -> VoiceCallResult:
response = requests.get(
f"{self.config['api_url'].rstrip('/')}/calls/{call_id}",
headers={
"Authorization": f"Bearer {self.config['api_token']}",
},
timeout=int(self.config.get("timeout", 10)),
)
response.raise_for_status()
data = response.json() if response.content else {}
return VoiceCallResult(
call_id=call_id,
status=str(data.get("status") or "unknown"),
raw=data,
)Set capability:
capabilities = VoiceProviderCapabilities(
tts=True,
status_callback=False,
dtmf_callback=False,
status_polling=True,
)