Skip to main content
Instead of polling the Calls API repeatedly to check for updates, you can register a webhook — an HTTPS endpoint on your server — and VInfer will push a POST request to it the moment a relevant event occurs. Webhooks make your integration event-driven: your system reacts to call completions, dispositions, and escalations in real time without any unnecessary API traffic.

Available Events

EventDescription
call.completedA call has finished (either connected and ended, or was not answered).
call.disposition_setA disposition label was assigned to a completed call.
call.escalatedA call was escalated to a human agent during the conversation.
campaign.startedA scheduled campaign has begun dialing.
campaign.pausedA running campaign was paused (via API or dashboard).
campaign.completedAll contacts in a campaign have been attempted and the campaign is finished.

Register a Webhook

POST /webhooks Registers a new webhook endpoint to receive events for your workspace.

Request Body

url
string
required
The HTTPS URL on your server that will receive event payloads. Must be publicly reachable by VInfer’s servers. HTTP (non-TLS) URLs are not accepted.
events
array
required
Array of event type strings to subscribe to. You can subscribe to one or all available events. Example: ["call.completed", "call.escalated"]. Pass ["*"] to subscribe to all events.
secret
string
Optional — a secret string used to sign webhook payloads with HMAC-SHA256. If provided, VInfer includes an X-VInfer-Signature header on every delivery so you can verify the payload originated from VInfer. See Signature Verification.

Example Request

curl https://api.vinfer.ai/v1/webhooks \
  -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.example.com/webhooks/vinfer",
    "events": ["call.completed", "call.escalated", "campaign.completed"],
    "secret": "whsec_your_random_secret_string"
  }'

Example Response

{
  "id": "wh_5Tq2nX8mB",
  "url": "https://your-server.example.com/webhooks/vinfer",
  "events": ["call.completed", "call.escalated", "campaign.completed"],
  "status": "active",
  "created_at": "2024-01-28T16:45:00Z"
}

List Webhooks

GET /webhooks Returns all registered webhooks for your workspace, including their subscription status and last delivery result.

Example Request

curl https://api.vinfer.ai/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY"

Delete a Webhook

DELETE /webhooks/{id} Permanently removes a registered webhook. VInfer will stop sending events to the associated URL immediately. This action cannot be undone — if you need to re-enable the webhook, register it again via POST /webhooks.

Example Request

curl https://api.vinfer.ai/v1/webhooks/wh_5Tq2nX8mB \
  -X DELETE \
  -H "Authorization: Bearer YOUR_API_KEY"

Event Payload Structure

All events share a common JSON envelope. The event field identifies the event type, id is a unique identifier for this delivery (useful for deduplication), timestamp is when the event was generated, and data contains the event-specific payload.
{
  "event": "call.completed",
  "id": "evt_abc123xyz",
  "timestamp": "2024-01-15T10:35:22Z",
  "data": { }
}

call.completed Event

{
  "event": "call.completed",
  "id": "evt_8Kp3mL7qN",
  "timestamp": "2024-02-01T09:16:25Z",
  "data": {
    "call_id": "cal_9Hm3kP7qZ",
    "campaign_id": "cmp_4Rv8sT1wX",
    "contact": {
      "phone": "+919876543210",
      "name": "Priya Sharma"
    },
    "status": "completed",
    "disposition": "escalated",
    "duration_seconds": 142,
    "language": "hi-IN",
    "started_at": "2024-02-01T09:14:03Z",
    "ended_at": "2024-02-01T09:16:25Z",
    "transcript_job_id": "job_2Kn7wR4pM",
    "recording_url": "https://recordings.vinfer.ai/signed/cal_9Hm3kP7qZ?token=eyJ...&expires=1706783785"
  }
}

call.escalated Event

{
  "event": "call.escalated",
  "id": "evt_3Wm9sR2pK",
  "timestamp": "2024-02-01T09:15:50Z",
  "data": {
    "call_id": "cal_9Hm3kP7qZ",
    "campaign_id": "cmp_4Rv8sT1wX",
    "contact": {
      "phone": "+919876543210",
      "name": "Priya Sharma"
    },
    "escalation_reason": "customer_requested_agent",
    "escalated_at": "2024-02-01T09:15:50Z",
    "language": "hi-IN",
    "summary": "Customer expressed interest and requested to speak with a human agent about loan renewal options."
  }
}
Use the id field in the event envelope to implement deduplication in your handler. In rare cases (network retries, failover), your endpoint may receive the same event more than once — storing processed event IDs lets you safely skip duplicates.

Signature Verification

When you register a webhook with a secret, VInfer signs every payload using HMAC-SHA256 and includes the signature in the X-VInfer-Signature header as sha256=<hex_digest>. Verifying this signature confirms the payload genuinely came from VInfer and has not been tampered with. To verify:
  1. Read the raw request body as bytes (do not parse JSON first).
  2. Compute the HMAC-SHA256 of the raw body using your secret as the key.
  3. Compare your computed digest to the value in X-VInfer-Signature (after stripping the sha256= prefix).
  4. Use a constant-time comparison to prevent timing attacks.
import hmac
import hashlib

def verify_vinfer_signature(payload_bytes: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

# In your Flask/FastAPI handler:
# payload_bytes = request.get_data()          # raw body, before JSON parsing
# header = request.headers.get("X-VInfer-Signature")
# if not verify_vinfer_signature(payload_bytes, header, "whsec_your_secret"):
#     return 401
Always verify the signature in production. Without verification, any party who discovers your webhook URL can send forged events to your endpoint. Never use string equality (===) for signature comparison — use a constant-time function to prevent timing side-channel attacks.

Retry Behavior

If your endpoint does not respond with a 2xx status code within 10 seconds, VInfer treats the delivery as failed and retries with exponential backoff:
AttemptDelay after previous attempt
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry8 hours
After 5 failed retries, VInfer stops attempting delivery for that event. The failed delivery is logged in the dashboard under Settings → Webhooks → Delivery Log so you can investigate and replay events manually if needed.
Respond with 200 OK as quickly as possible — ideally before doing any heavy processing. Push the event payload to an internal queue (e.g., Redis, SQS, or a database task table) and process it asynchronously. This keeps your endpoint fast and prevents timeouts from triggering unnecessary retries.