Webhooks

Receive real-time HTTP notifications when events happen in your JagCall account. No polling required.

What Are Webhooks?

Webhooks are HTTP POST requests sent from JagCall to your server whenever a specific event occurs, such as a call completing or an SMS being received. Instead of polling the API, your application receives updates in real time.

Each webhook request includes a JSON payload describing the event, a timestamp, and an HMAC-SHA256 signature for verification.

Configuration

You can configure webhook URLs at two levels:

  • Per agent: In the agent settings, add a webhook URL that receives events only for that agent.
  • Organization-wide: In Settings → Webhooks, add a URL that receives all events across your organization.

When both are configured, both URLs receive the event. Your endpoint must return a 2xx status code within 10 seconds to acknowledge receipt.

Event Types

call.started

Fired when an inbound or outbound call connects.

Payload

{
  "event": "call.started",
  "timestamp": "2025-11-01T14:30:00Z",
  "data": {
    "call_id": "call_xxx",
    "agent_id": "agt_xxx",
    "direction": "inbound",
    "from": "+14155559876",
    "to": "+14155551234"
  }
}
call.completed

Fired when a call ends normally.

Payload

{
  "event": "call.completed",
  "timestamp": "2025-11-01T14:32:22Z",
  "data": {
    "call_id": "call_xxx",
    "agent_id": "agt_xxx",
    "duration_seconds": 142,
    "status": "completed",
    "recording_available": true
  }
}
call.failed

Fired when a call fails to connect or encounters an error.

Payload

{
  "event": "call.failed",
  "timestamp": "2025-11-01T14:30:05Z",
  "data": {
    "call_id": "call_xxx",
    "agent_id": "agt_xxx",
    "error_code": "no_answer",
    "error_message": "The call was not answered after 30 seconds."
  }
}
call.transcript.ready

Fired when the full transcript is available after a call.

Payload

{
  "event": "call.transcript.ready",
  "timestamp": "2025-11-01T14:33:00Z",
  "data": {
    "call_id": "call_xxx",
    "agent_id": "agt_xxx",
    "transcript_url": "https://jagcall.com/v1/calls/call_xxx/transcript"
  }
}
sms.sent

Fired when an outbound SMS is sent.

Payload

{
  "event": "sms.sent",
  "timestamp": "2025-11-01T15:00:00Z",
  "data": {
    "sms_id": "sms_xxx",
    "from": "+14155551234",
    "to": "+14155559876",
    "body": "Your appointment is confirmed.",
    "status": "sent"
  }
}
sms.received

Fired when an inbound SMS is received on one of your numbers.

Payload

{
  "event": "sms.received",
  "timestamp": "2025-11-01T15:05:00Z",
  "data": {
    "sms_id": "sms_yyy",
    "from": "+14155559876",
    "to": "+14155551234",
    "body": "Thanks, see you then!"
  }
}

Signature Verification

Every webhook request includes two headers for verification:

  • X-JagCall-Signature — HMAC-SHA256 hex digest of {timestamp}.{body}
  • X-JagCall-Timestamp — Unix timestamp of when the webhook was sent

Your webhook signing secret is available in Settings → Webhooks. Always verify signatures to prevent spoofing.

python
import hmac
import hashlib

def verify_webhook(payload_body: bytes, signature: str, timestamp: str, secret: str) -> bool:
    """Verify the HMAC-SHA256 signature of a JagCall webhook."""
    signed_content = f"{timestamp}.{payload_body.decode('utf-8')}"
    expected = hmac.new(
        secret.encode("utf-8"),
        signed_content.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
node.js
import crypto from "crypto";

function verifyWebhook(
  payloadBody: string,
  signature: string,
  timestamp: string,
  secret: string
): boolean {
  const signedContent = `${timestamp}.${payloadBody}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signedContent)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Reject webhooks with a timestamp older than 5 minutes to prevent replay attacks. Compare the X-JagCall-Timestamp header against the current time.

Retry Policy

If your endpoint does not return a 2xx status code within 10 seconds, JagCall retries with exponential backoff:

AttemptDelay
1st retry30 seconds
2nd retry2 minutes
3rd retry15 minutes
4th retry1 hour
5th retry (final)6 hours

After 5 failed retries, the event is marked as failed. You can view failed events and manually retry them from Settings → Webhooks → Event Log.

Testing Webhooks

During development, you can test webhooks without deploying:

  1. Use a tunnel service like ngrok to expose your local server:
    ngrok http 3000
  2. Copy the HTTPS URL and paste it as your webhook URL in Settings.
  3. Use the Send Test Event button in the webhook settings to trigger a sample event.
  4. Inspect the request in your server logs or ngrok dashboard.