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

# Plivo vs Vobiz: Webhooks, Callbacks & Signature Validation

> Map Plivo answer/ring/hangup callbacks and X-Plivo-Signature-V3 (HMAC) verification to Vobiz answer_url events and X-Vobiz-Signature-V2/V3. Includes before/after verification code for Python and Node.

Plivo and Vobiz handle voice webhooks almost identically: both POST `application/x-www-form-urlencoded` callbacks with the same call params, and both sign each request with an HMAC keyed by your auth token plus a nonce. The two differences that matter: Vobiz sends `Ring` / `StartApp` / `Hangup` as events to the answer flow (no separate `ring_url`), and Vobiz V2/V3 sign **only** the base URL + nonce, while Plivo V3 also hashes the sorted POST params.

## Callback URL & event mapping

| Plivo                                              | Vobiz                                             | Notes                                                                  |
| -------------------------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------- |
| `answer_url` (mandatory)                           | `answer_url` + `answer_method`                    | Returns XML to drive the call.                                         |
| `ring_url`                                         | *(no separate URL)* — `Ring` event to answer flow | Vobiz emits a `Ring` callback instead of a dedicated endpoint.         |
| `hangup_url` + `hangup_method`                     | `hangup_url` + `hangup_method`                    | Fire-and-forget end-of-call notification; also emits a `Hangup` event. |
| `fallback_url` / `fallback_answer_url`             | `fallback_answer_url` + `fallback_method`         | Invoked when the primary answer URL is unreachable.                    |
| `action` attribute (`Dial`, `Record`, `GetInput`…) | `action` attribute (`Dial`, `Record`, `Gather`…)  | Expects an XML response to continue.                                   |
| `callbackUrl` attribute (notify-only)              | `callbackUrl` / `callback_url` attribute          | Async notification; no XML expected.                                   |
| Event: answered                                    | Event: `StartApp`                                 | "Call answered."                                                       |
| Event: hangup                                      | Event: `Hangup`                                   | "Call ended."                                                          |
| Event: ringing                                     | Event: `Ring`                                     | "Call is ringing."                                                     |

## Signature header mapping

| Plivo                                | Vobiz                               | Scheme                                                                                 |
| ------------------------------------ | ----------------------------------- | -------------------------------------------------------------------------------------- |
| `X-Plivo-Signature-V3`               | `X-Vobiz-Signature-V3`              | HMAC-SHA256, base64. Signs `baseURL + "." + nonce`.                                    |
| `X-Plivo-Signature-V3-Nonce`         | `X-Vobiz-Signature-V3-Nonce`        | Random nonce for the V3 signature.                                                     |
| *(no equivalent)*                    | `X-Vobiz-Signature-V2` (+ `-Nonce`) | HMAC-SHA256, base64. Signs `baseURL + nonce` (no `.`).                                 |
| `X-Plivo-Signature-Ma-V3`            | `X-Vobiz-Signature-MA-V3`           | Same scheme, signed with the **main (parent) account** token on sub-account callbacks. |
| `X-Plivo-Signature-V2` (legacy SHA1) | `X-Vobiz-Signature` (legacy SHA1)   | Backwards compatibility only — avoid.                                                  |

<Note>
  **The signed strings are not byte-identical.** Plivo V3 signs the full URL + sorted POST params + nonce; Vobiz V3 strips query params and signs only `baseURL + "." + nonce`. You cannot reuse Plivo's `validate_v3_signature(...)` against Vobiz headers — use the HMAC below.
</Note>

## Before / after: verifying the signature

Plivo ships a helper; on Vobiz verification is a few lines of stdlib HMAC (the `vobiz` SDK is for the REST API, not webhook validation).

```python Python (Flask) theme={null}
# ---------- BEFORE: Plivo ----------
import plivo
from flask import request, abort

AUTH_TOKEN = "your_plivo_auth_token"

@app.route("/webhook", methods=["POST"])
def webhook_plivo():
    valid = plivo.utils.validate_v3_signature(
        request.method,
        request.url,
        request.headers.get("X-Plivo-Signature-V3-Nonce", ""),
        AUTH_TOKEN,
        request.headers.get("X-Plivo-Signature-V3", ""),
        request.form.to_dict(),   # Plivo hashes the POST params too
    )
    if not valid:
        abort(403, "Invalid signature")
    # ... handle event

# ---------- AFTER: Vobiz ----------
import hmac, hashlib, base64
from urllib.parse import urlparse, urlunparse
from flask import request, abort

AUTH_TOKEN = "your_vobiz_auth_token"

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

def validate(url, token, headers, v3=True):
    sep = "." if v3 else ""
    sig_h   = "X-Vobiz-Signature-V3" if v3 else "X-Vobiz-Signature-V2"
    nonce_h = sig_h + "-Nonce"
    msg = (base_url(url) + sep + headers.get(nonce_h, "")).encode()
    expected = base64.b64encode(
        hmac.new(token.encode(), msg, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(headers.get(sig_h, ""), expected)

@app.route("/webhook", methods=["POST"])
def webhook_vobiz():
    if not validate(request.url, AUTH_TOKEN, request.headers, v3=True):
        abort(403, "Invalid signature")
    event = request.form.get("Event")   # Ring | StartApp | Hangup
    # ... handle event
    return "", 200
```

Callback params are the same names you read from Plivo — `CallUUID`, `From`, `To`, `Direction`, `CallStatus`, `HangupCause`, `Duration`, plus `Event` / `timestamp` / `auth_id` on every callback. See the [Vobiz XML request params](/xml/request) and [Callbacks reference](/concepts/callbacks).

## Key differences & gotchas

* **Different signed string.** Vobiz V2/V3 hash only the base URL + nonce, so the signature proves origin and URL, not body integrity — treat param values defensively.
* **Events instead of dedicated URLs.** Branch on the `Event` field for `Ring`, `StartApp`, and `Hangup` rather than registering separate `ring_url` / `hangup_url`.
* **Header casing & compares.** Nonces are 20-digit strings; read headers case-insensitively and compare with `hmac.compare_digest` / `crypto.timingSafeEqual`, never `==`.
* **Main-account (MA) signatures.** Sub-account callbacks add a parent-signed header (`X-Vobiz-Signature-MA-V3`) — reuse the validator with the parent token.

## What has no direct equivalent

* **No SDK signature helper on Vobiz.** Plivo bundles `validate_v3_signature` / `validateV3Signature`; the Vobiz SDKs do not ship a webhook validator — use the stdlib HMAC snippet above.
* **No comma-separated multi-token signatures.** Plivo concatenates signatures for multiple active tokens in one header; Vobiz documents a single signature per scheme (plus the MA variant). Rotate the token rather than running two in parallel.
* **No dedicated `ring_url` endpoint.** There is no separate ring callback URL — the `Ring` event is delivered through the answer flow.

<CardGroup cols={2}>
  <Card title="Validating callbacks" icon="shield-check" href="/concepts/validating-callbacks">
    Canonical Vobiz signature reference with Python, Node, Go, and Ruby.
  </Card>

  <Card title="Migration gotchas" icon="triangle-exclamation" href="/guides/plivo-to-vobiz/gotchas">
    Edge cases when moving from Plivo to Vobiz.
  </Card>
</CardGroup>
