Call Completed Webhook

The call_tracking.call.completed webhook - one signed POST per call on a tracking number with caller, status, duration, price, match type and the visit's UTM tags and click IDs. Subscribe, verify and retry.

2 min read

Edesy sends call_tracking.call.completed once for every call on a tracking number, after the call ends. Use it to put each call and its campaign into your CRM, database or reporting.

Subscribe#

Add a webhook subscription in the voice app (voice-app.edesy.in → Webhooks) or through the API:

curl -X POST https://voice-api.edesy.in/v1/webhooks/subscriptions \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/edesy",
    "event_types": ["call_tracking.call.completed"],
    "secret": "a-long-random-string"
  }'

Leave event_types empty to receive every voice event. Send a test with POST/v1/webhooks/subscriptions/{id}/test.

Request#

POST to your URL with Content-Type: application/json and these headers:

Header Value
X-Edesy-Event call_tracking.call.completed
X-Edesy-Signature sha256=<hex>: HMAC-SHA256 of the raw body with your secret (when a secret is set)

Payload#

{
  "event": "call_tracking.call.completed",
  "timestamp": "2026-11-14T06:12:44Z",
  "data": {
    "call_sid": "CA_...",
    "started_at": "2026-11-14T06:10:02Z",
    "caller": "+9198XXXXXXXX",
    "tracking_number": "+9122XXXXXXXX",
    "status": "completed",
    "duration_sec": 154,
    "price": 3,
    "connected_number": "+9190XXXXXXXX",
    "pool_id": "4b1d...",
    "match": "exact",
    "is_first_call": true,
    "recording_available": false,
    "attribution": {
      "match": "exact",
      "session_id": "9e2c...",
      "site_host": "www.example.com",
      "landing_url": "https://www.example.com/flats?utm_source=google&utm_medium=cpc&utm_campaign=2bhk&gclid=Cj0KCQiA...",
      "referrer": "https://www.google.com/",
      "utm_source": "google",
      "utm_medium": "cpc",
      "utm_campaign": "2bhk",
      "utm_term": "2bhk flats",
      "gclid": "Cj0KCQiA...",
      "first_touch": {
        "utm_source": "facebook",
        "utm_medium": "paid_social",
        "fbclid": "IwAR...",
        "landing_url": "https://www.example.com/",
        "at": "2026-11-02T14:03:11Z"
      }
    }
  }
}
Field Meaning
match exact, grace, repeat_caller or none. See Attribution Rules
is_first_call The caller had never called this pool before
price What the call cost from your wallet, in rupees
connected_number The phone that answered, when the call connected
attribution The credited visit; only match is set when the call is unattributed. Empty fields are left out
recording_available Whether the call has a recording

Verify the signature#

Compute HMAC-SHA256 over the raw request body with your secret and compare it to X-Edesy-Signature in constant time.

import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return header && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

You can check a signature by hand with the webhook signature verifier.

Retries#

Respond with any 2xx within 5 seconds. Any other response, or a timeout, is retried with backoff starting at 30 seconds and capped at one hour, up to 6 attempts in total. Failed deliveries can be replayed from the subscription's delivery log. Use call_sid to ignore duplicates.

FAQ#

Is the event sent for unanswered calls? Yes. It is sent for every call on a tracking number; check status and duration_sec.

Is the event sent when the call was unattributed? Yes, with attribution.match set to none.

Can I get the event in Call Events v1 format? call_tracking.call.completed is delivered to standard (legacy-format) subscriptions with the X-Edesy-Signature header.

Was this page helpful?