> ## Documentation Index
> Fetch the complete documentation index at: https://www.vobiz.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Twilio to Vobiz Code: Call Records, Webhooks and Errors

> Side-by-side Twilio and Vobiz code for listing call records (CDRs), validating webhook signatures (X-Vobiz-Signature-V3, HMAC-SHA256) and handling API errors.

Part of the [Twilio to Vobiz code snippets](https://www.vobiz.ai/docs/compare/twilio/code-snippets): each task shows the **Twilio** code and the equivalent **Vobiz** code in Python and Node - copy the Vobiz tab. The snippets use the `client` from [Set up the client](https://www.vobiz.ai/docs/compare/twilio/code-snippets#set-up-the-client).

> [!NOTE]
>
> Set your credentials once: `AUTH_ID` is your Vobiz Auth ID (`MA_…`), `AUTH_TOKEN` is your Auth Token. In the Vobiz SDK, `api_key` **is** the Auth ID, and every account-scoped method takes that `auth_id` explicitly.

## List call records

Completed-call history is a first-class `cdr` resource on Vobiz, with rich filters for reporting.

| | Twilio | Vobiz |
| - | - | - |
| Method | `client.calls.list(status="completed", limit=20)` | `client.cdr.list_cdrs(AUTH_ID, start_date=..., end_date=..., call_direction=...)` |
| Filter by | Call status | Date range, direction, numbers, hangup cause, and more. See [List CDRs](https://www.vobiz.ai/docs/cdr/list-cdrs.md#filters). |

```python Twilio · Python theme={null}
records = client.calls.list(status="completed", limit=20)
for r in records:
    print(r.sid, r.to, r.duration, r.price)
```

```python Vobiz · Python theme={null}
records = client.cdr.list_cdrs(
    AUTH_ID,
    start_date="2026-06-01",
    end_date="2026-06-30",
    call_direction="outbound",
)
```

> [!TIP]
>
> Filter CDRs by `from_number`, `to_number`, `call_direction`, `hangup_cause`, `bridge_uuid`, or `sip_call_id` - purpose-built for reporting. See [Voice Call API](https://www.vobiz.ai/docs/compare/twilio/voice-call-api.md) for the live-vs-history split (`live_calls` for in-flight legs, `cdr` for completed history).

## Validate a webhook signature

Both platforms sign inbound webhooks so you can prove the request came from them. Twilio's `RequestValidator` rebuilds the full URL plus every sorted POST field and HMAC-SHA1s it against `X-Twilio-Signature`. Vobiz signs a short, deterministic string - `baseURL + "." + nonce` (query stripped) - with HMAC-SHA256, and sends it as `X-Vobiz-Signature-V3` with the random nonce in `X-Vobiz-Signature-V3-Nonce`. No param-sorting step, reproducible in any language with the standard library.

| | Twilio | Vobiz |
| - | - | - |
| Header | `X-Twilio-Signature` | `X-Vobiz-Signature-V3`, with `X-Vobiz-Signature-V3-Nonce` |
| Algorithm | HMAC-SHA1 | HMAC-SHA256 |
| Message signed | Full URL plus every sorted POST field | Base URL (query stripped) + `.` + nonce |
| Key | Auth Token | Auth Token |
| Validator | `RequestValidator(AUTH_TOKEN).validate(...)` | A short `validate_v3` function, shown below. See [Validating callbacks](https://www.vobiz.ai/docs/concepts/validating-callbacks.md). |

```python Twilio · Python theme={null}
from flask import Flask, request, abort
from twilio.request_validator import RequestValidator

app = Flask(__name__)
AUTH_TOKEN = "your_twilio_auth_token"

@app.route("/voice", methods=["POST"])
def voice():
    validator = RequestValidator(AUTH_TOKEN)
    # HMAC-SHA1 over full URL + sorted POST params
    valid = validator.validate(
        request.url,
        request.form,
        request.headers.get("X-Twilio-Signature", ""),
    )
    if not valid:
        abort(403)
    # ... return TwiML
```

```python Vobiz · Python theme={null}
import hmac, hashlib, base64
from urllib.parse import urlparse, urlunparse
from flask import Flask, request, abort

app = Flask(__name__)
AUTH_TOKEN = "your_vobiz_auth_token"      # your Auth Token signs the webhook

def _base_url(url: str) -> str:
    p = urlparse(url)
    return urlunparse((p.scheme, p.netloc, p.path, "", "", ""))  # strip query

def validate_v3(url: str, headers, token: str) -> bool:
    nonce = headers.get("X-Vobiz-Signature-V3-Nonce", "")   # read case-insensitively
    msg = (_base_url(url) + "." + nonce).encode()            # HMAC-SHA256
    expected = base64.b64encode(
        hmac.new(token.encode(), msg, hashlib.sha256).digest()
    ).decode()
    # constant-time compare - never ==
    return hmac.compare_digest(headers.get("X-Vobiz-Signature-V3", ""), expected)

@app.route("/voice", methods=["POST"])
def voice():
    if not validate_v3(request.url, request.headers, AUTH_TOKEN):
        abort(403)
    # ... return VobizXML
    return "", 200
```

> [!NOTE]
>
> On sub-account callbacks Vobiz also adds `X-Vobiz-Signature-MA-V3`, signed with the **parent (main-account)** token, so a parent can verify child traffic with the same validator. Full mapping and the Node version: [webhooks & signatures](https://www.vobiz.ai/docs/compare/twilio/webhooks.md).

## Handle errors

Catch the Vobiz error types (all subclasses of `ApiError`) the same way you caught Twilio's `TwilioRestException` - with the HTTP status and response body available on the exception.

| | Twilio | Vobiz |
| - | - | - |
| Import | `from twilio.base.exceptions import TwilioRestException` | `from vobiz.core.api_error import ApiError` |
| Catch | `except TwilioRestException as e` | `except ApiError as e` |
| Details | `e.status`, `e.code`, `e.msg` | `e.status_code`, `e.body` |

```python Twilio · Python theme={null}
from twilio.base.exceptions import TwilioRestException

try:
    client.calls.create(
        to="+14165553434",
        from_="+14155551234",
        url="https://example.com/answer.xml",
        method="POST",
    )
except TwilioRestException as e:
    print(e.status, e.code, e.msg)
```

```python Vobiz · Python theme={null}
from vobiz.core.api_error import ApiError

try:
    client.calls.make_call(
        auth_id=AUTH_ID,
        from_="+14155551234",
        to="+14165553434",
        answer_url="https://example.com/answer.xml",
        answer_method="POST",
    )
except ApiError as e:
    print(e.status_code, e.body)
```

> [!TIP]
>
> Full verb table: [TwiML → VobizXML](https://www.vobiz.ai/docs/compare/twilio/twiml-to-vobizxml.md).

## More Twilio to Vobiz code snippets

* [Set up the client and make an outbound call](https://www.vobiz.ai/docs/compare/twilio/code-snippets)
* [Answer calls with XML](https://www.vobiz.ai/docs/compare/twilio/code-snippets/answer-calls.md)
* [Live calls](https://www.vobiz.ai/docs/compare/twilio/code-snippets/live-calls.md)
* [Numbers and conferences](https://www.vobiz.ai/docs/compare/twilio/code-snippets/numbers-and-conferences.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.