Skip to main content

RealCall Webhooks Reference

Technical documentation for RealCall webhooks. Describes how to configure an HTTPS receiver and verify signed POST payloads (with optional API-key authentication) for the stream.concluded event.

Written by Wen Huang

Overview

RealCall can notify your systems via HTTP webhooks when a call or stream finishes processing and a conclusion is reached. This article describes the webhook interface for implementers building a receiver.

When a subscribed event occurs, RealCall sends an HTTP POST to your registered URL with a JSON payload. Each request is signed with an Ed25519 signature so you can verify it came from RealCall and hasn't been tampered with in transit.


Prerequisites

Before setting up a webhook, ensure the following are in place:

  • Active account: Your organization must have an active account on the Reality Defender platform with access to the RealCall dashboard.

  • HTTPS endpoint: Your receiver URL must use HTTPS with a valid, publicly trusted TLS certificate. See TLS Requirements below.

  • Public key storage: When you create a webhook, RealCall displays the Ed25519 public key once. Have a secure location ready to store it.


Setting Up a Webhook

You can create webhooks in the RealCall dashboard.

Step 1: Open the Create Webhook dialog

In the bottom-left corner, click your username, then go to Settings > Webhooks and click Add Webhook. The Create Webhook dialog opens.

Step 2: Fill in the fields

Field

Required

Description

Name

Yes

A label to identify this webhook in the dashboard (e.g. My Webhook).

URL

Yes

The HTTPS endpoint that will receive POST requests (e.g. https://example.com/webhook). Must use HTTPS with a valid TLS certificate.

API Key

No

Optional authentication token. If set, it is sent as the X-API-Key header with every delivery.

Events

Yes

Select at least one event. Currently only Stream Concluded (stream.concluded) is available and is selected by default.

Step 3: Click Create

RealCall displays the public key for this webhook (base64-encoded Ed25519). Copy and store it now — it is shown only once and cannot be retrieved later. Use it to verify webhook signatures on your server (see Authentication & Verification).

Note: If you lose the public key, delete the webhook and create a new one to receive a fresh key pair.

The webhook is enabled by default once created. You can edit, disable, or delete it from the same Settings > Webhooks page.


Delivery

Each delivery is a single HTTP POST request.

Property

Value

Method

POST

Content-Type

application/json

Timeout

10 seconds

Success

Any 2xx response status

Failure

Any non-2xx status, timeout, or connection error

Note: There is currently no automatic retry. A failed delivery is logged but not re-sent. Your endpoint should respond 2xx as soon as it accepts the payload and perform any slow processing asynchronously.

Headers sent with every delivery

Header

Description

Content-Type

Always application/json

User-Agent

RealityDefender-Webhook/1.0

X-Signature-Ed25519

Base64-encoded Ed25519 signature of the raw request body

X-API-Key

Only present if you configured an API key on the webhook


Payload Format

The body is a JSON envelope with event, timestamp, and data fields:

{   
"event": "stream.concluded",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"stream_id": "str_abc123",
"organization_id": "org_xyz789",
"conclusion": "ARTIFICIAL",
"probability": 0.95,
"session_id": "call_001"
}
}
  • event — The event type.

  • timestamp — UTC time the event was emitted (RFC 3339 / ISO 8601).

  • data — Event-specific fields.


Events

Event

Trigger

data fields

stream.concluded

A call or stream finishes processing and a conclusion is reached

stream_id, organization_id, conclusion, probability, session_id

stream.concluded is currently the only event type.

data fields for stream.concluded

Field

Type

Description

stream_id

string

Unique identifier of the analyzed stream.

organization_id

string

Your organization identifier.

conclusion

string

The verdict (see values below). Omitted if no result exists.

probability

number

Confidence score, 0.0–1.0. Omitted if no result exists.

session_id

string

The originating call or session identifier (e.g. SIP call ID).

conclusion values

Value

Meaning

AUTHENTIC

Determined to be a real human voice.

ARTIFICIAL

Determined to be AI-generated.

SUSPICIOUS

Shows signs of manipulation but below the ARTIFICIAL threshold.

INCONCLUSIVE

Analysis ran but could not reach a confident determination.

If no result record exists for the stream, conclusion and probability are omitted from data.


TLS Requirements

TLS is required for all webhook endpoints. RealCall delivers webhooks over HTTPS and verifies the server's TLS certificate using standard public CA trust. Delivery fails if the certificate cannot be verified.

Requirement

Details

HTTPS URL

The webhook URL must use https://. Plain http:// may work only for local testing and is not suitable for staging or production.

Publicly trusted certificate

The certificate must be signed by a publicly trusted CA (e.g. Let's Encrypt, DigiCert). Self-signed certificates and private or internal CAs are not accepted.

Full certificate chain

The server must present the leaf certificate and all intermediate certificates. A missing intermediate chain causes verification failures.

Hostname match

The hostname in the webhook URL must match the certificate's Subject Alternative Name (SAN). Use a DNS name (e.g. https://webhooks.example.com/...) rather than a raw IP address unless the certificate explicitly includes that IP in its SAN.

Common causes of delivery failure

Error or symptom

Likely cause

x509: certificate signed by unknown authority

Self-signed cert, private CA, or incomplete certificate chain.

x509: certificate is valid for ...

Webhook URL uses an IP or hostname that does not match the certificate.

Connection timeout

Port 443 is not reachable from the internet, or a firewall is blocking inbound HTTPS.

Setting up TLS

A typical setup for a webhook receiver:

  1. Point a DNS name at your server (e.g. webhooks.example.com → your server's IP).

  2. Obtain a certificate from a public CA (e.g. Let's Encrypt via Certbot).

  3. Configure your web server to serve the full certificate chain (fullchain.pem in Certbot/nginx terminology).

  4. Register the webhook URL using that hostname: https://webhooks.example.com/your/webhook/path

  5. Verify from outside before going live:

curl -v <https://webhooks.example.com/your/webhook/path>

Confirm the output includes SSL certificate verify ok.


Note: Do not register a webhook URL with a raw IP address (e.g. https://203.0.113.10/...) unless you have a publicly trusted certificate that explicitly includes that IP. Using a DNS hostname with a standard CA-issued certificate is strongly recommended.


Authentication & Verification

Two independent mechanisms are available. You can use either, both, or neither.

Ed25519 signature (recommended)

When you create a webhook, RealCall generates an Ed25519 key pair. The private key is stored on RealCall's side and used to sign every payload; the base64-encoded public key is returned to you once at creation time — store it securely.

Every request includes an X-Signature-Ed25519 header: a base64-encoded Ed25519 signature of the raw request body bytes. To verify:

  1. Read the raw request body (the exact bytes, before any JSON parsing).

  2. Base64-decode the X-Signature-Ed25519 header.

  3. Base64-decode your stored public key.

  4. Run Ed25519 verification over the raw body using the public key.

Note: Verify against the raw bytes. If you re-serialize parsed JSON, the bytes (and therefore the signature) may differ and verification will fail.

API key (optional)

The API key is an optional static token you provide when creating the webhook. If set, RealCall forwards it as the X-API-Key header on every delivery. This is useful when your receiver sits behind an API gateway or firewall that requires a static token.

The API key is independent of the signature: the signature proves the payload came from RealCall, while the API key authenticates the request to your server.


Python Example

A complete receiver using Flask that verifies the Ed25519 signature and, optionally, the API key before processing the event. Requires the cryptography library.

pip install flask cryptography
import base64 
import os

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from flask import Flask, request, abort

app = Flask(__name__)

# Base64 public key shown once when the webhook was created in RealCall. WEBHOOK_PUBLIC_KEY_B64 = os.environ["REALCALL_WEBHOOK_PUBLIC_KEY"]

# Optional: the API key you configured on the webhook (if any). WEBHOOK_API_KEY = os.environ.get("REALCALL_WEBHOOK_API_KEY")

_public_key = Ed25519PublicKey.from_public_bytes( base64.b64decode(WEBHOOK_PUBLIC_KEY_B64) )

def verify_signature(raw_body: bytes, signature_header: str) -> bool:
if not signature_header:
return False
try:
signature = base64.b64decode(signature_header)
_public_key.verify(signature, raw_body)
return True
except (InvalidSignature, ValueError):
return False

@app.post("/webhook")
def webhook():
# 1. Read the RAW body bytes (do not use request.json here first).
raw_body = request.get_data()

# 2. Verify the Ed25519 signature.
if not verify_signature(raw_body, request.headers.get("X-Signature-Ed25519", "")):
abort(401, "invalid signature")

# 3. (Optional) Check the API key, if you configured one.
if WEBHOOK_API_KEY and request.headers.get("X-API-Key") != WEBHOOK_API_KEY:
abort(401, "invalid api key")

# 4. Signature valid — now it is safe to parse and handle the payload.
payload = request.get_json()
event = payload.get("event")
data = payload.get("data", {})

if event == "stream.concluded":
print(
f"stream {data.get('stream_id')} concluded: "
f"{data.get('conclusion')} (probability={data.get('probability')})"
)
# TODO: enqueue async processing here; keep this handler fast.

# 5. Respond 2xx promptly so the delivery is marked successful.
return "", 200

if __name__ == "__main__":
app.run(port=9999)

To run:

export REALCALL_WEBHOOK_PUBLIC_KEY="<base64 public key from webhook creation>" 
# export REALCALL_WEBHOOK_API_KEY="<your api key>" # only if configured
python app.py

Verifying without Flask

The verification logic only requires the raw body, the signature header, and the public key:

import base64 
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

def is_valid(raw_body: bytes, signature_b64: str, public_key_b64: str) -> bool:
public_key = Ed25519PublicKey.from_public_bytes(base64.b64decode(public_key_b64))
try:
public_key.verify(base64.b64decode(signature_b64), raw_body)
return True
except (InvalidSignature, ValueError):
return False

Receiver Checklist

  • Expose an HTTPS endpoint with a publicly trusted certificate and full chain (see TLS Requirements).

  • Use a DNS hostname in the webhook URL that matches the certificate.

  • Accept POST requests with a JSON body.

  • Read and verify the raw body against X-Signature-Ed25519 using your stored public key.

  • (Optional) Validate the X-API-Key header if you configured an API key.

  • Respond with a 2xx status within 10 seconds.

  • Perform slow processing asynchronously — there are no automatic retries.

Did this answer your question?