Webhooks
Webhooks move call data between Moja and your systems in real time. They come in two directions, and the direction determines almost everything else about how you configure them.
| Direction | Portal location | What it does |
|---|---|---|
| Outgoing | Webhooks → Outgoing | Moja sends an HTTP request to your endpoint when a call event fires |
| Incoming | Webhooks → Incoming | You send an HTTP request to Moja to pass caller data, update revenue, update tags, or update fields |
Outgoing webhooks
Section titled “Outgoing webhooks”Events
Section titled “Events”Each outgoing webhook triggers on one event type. These are the common ones. The complete and current list is in Webhooks → Outgoing in the dashboard when you create or edit a webhook — treat the dashboard as authoritative if it disagrees with this table.
| Event | Fires when |
|---|---|
call.incoming |
A caller dials a tracking number and the platform begins processing |
call.connected |
The call connects to a target |
call.hangup |
Either party ends the call |
call.failure |
The call cannot be connected to a target — busy, no answer, or error |
call.completed |
Post-call processing finishes, including conversion evaluation and logging |
call.converted |
The call meets the conversion criteria. Fires after hangup |
call.on_5_secs_mark |
Connected duration reaches 5 seconds |
call.on_15_secs_mark |
Connected duration reaches 15 seconds |
call.on_30_secs_mark |
Connected duration reaches 30 seconds |
call.on_45_secs_mark |
Connected duration reaches 45 seconds |
call.on_60_secs_mark |
Connected duration reaches 60 seconds |
Payload shape
Section titled “Payload shape”There is no fixed Moja payload. You define the entire request — URL, HTTP method, headers, and body — as a template, and Moja substitutes [TAG_NAME] placeholders with real call data at dispatch time.
Supported methods: GET, POST, PUT, PATCH, DELETE.
Supported body formats: JSON (application/json) and form data (application/x-www-form-urlencoded).
{ "call_id": "[CALL_ID]", "caller": "[CALLER_ID]", "campaign": "[CAMPAIGN_NAME]", "duration": "[DURATION]", "converted": "[CONVERTED]", "revenue": "[REVENUE_PAID_OUT]", "tags": { "source": "[UTM_SOURCE]", "state": "[CALLER_STATE]" }}{ "call_id": "call_abc123def456", "caller": "+13105559876", "campaign": "Medicare Leads Q1", "duration": "145", "converted": "true", "revenue": "35.00", "tags": { "source": "google_ads", "state": "CA" }}Placeholders work in the URL and query string too:
https://your-api.com/webhook?call_id=[CALL_ID]&campaign=[CAMPAIGN_ID]Configuration fields
Section titled “Configuration fields”| Field | Tier | Notes |
|---|---|---|
| Endpoint URL | Required | Use HTTPS |
| Event type | Required | One event per webhook. Create several webhooks to cover several events |
| HTTP method | Required | GET, POST, PUT, PATCH, or DELETE |
| Body template | Required for methods with a body | Uses [TAG_NAME] placeholders |
| Body format | Required | JSON or form data |
| Custom headers | Recommended | Where you put your own Authorization header or a shared secret |
| Campaign or target attachment | Required to receive traffic | Attach under Campaign → Outgoing webhooks, or on a target or RTB formula |
Tags are the placeholder vocabulary. The full, current list appears in the dashboard when you build the template; the groups below are the ones most integrations use.
Call identification — [CALL_ID], [CALL_SID], [INBOUND_CALL_ID], [ORG_ID], [FROM], [TO], [CALLER_ID], [CALLER_ID_NO_PLUS], [CALLER_ID_NUMERIC], [CALLER_ZIP], [CALLER_STATE], [STATE], [ZIP_CODE], [ORIGINAL_STATE], [ORIGINAL_ZIP_CODE]
Campaign and publisher — [CAMPAIGN_ID], [CAMPAIGN_NAME], [PUBLISHER_ID], [PUBLISHER_NAME], [PUBLISHER_NUMBER], [PAYOUT], [PAYOUT_NUMERIC]
Target and buyer — [TARGET_ID], [TARGET_NAME], [TARGET_TYPE], [BUYER_ID], [BUYER_NAME], [TARGET_PAYOUT], [PUBLISHER_PAYOUT]
Conversion and revenue — [CONVERTED], [REVENUE_PAID_OUT], [PUBLISHER_PAID_OUT], [DURATION], [INBOUND_DURATION], [OUTBOUND_DURATION], [CALL_TERMINATION_BY], [CALL_CONVERTED_AT], [EVENT_TIMESTAMP], [CONFIGURED_PUBLISHER_PAYOUT], [CONFIGURED_BUYER_REVENUE], [OVERWRITTEN_BUYER_REVENUE], and the _NUMERIC variants of the configured amounts
Attribution — populated for calls that came through DNI: [GCLID], [FBCLID], [UTM_SOURCE], [UTM_MEDIUM], [UTM_CAMPAIGN], [UTM_CONTENT], [UTM_TERM], [KEYWORD], [VISITOR_ID], [LANDING_PAGE], [LANDING_PAGE_DOMAIN], [LANDING_PAGE_PATH], [REFERRER], [VISITOR_CITY], [VISITOR_STATE], [VISITOR_ZIP], [VISITOR_COUNTRY], [VISITOR_IP], [VISITOR_BROWSER]
Custom tags — any custom tag you define is available as its key in uppercase. A tag with key lead_type becomes [LEAD_TYPE].
Incoming webhooks
Section titled “Incoming webhooks”Four types, each with its own endpoint and request shape. Create them under Webhooks → Incoming; saving generates the URL and auth key.
| Type | Endpoint | Purpose |
|---|---|---|
call_data |
/call-data |
Pass caller metadata before or during routing |
update_revenue |
/update-revenue |
Update revenue and conversion data on a call |
update_tags |
/update-tags |
Add or remove custom tags on a call |
update_fields |
/update-fields |
Update several call record fields in one request |
All four authenticate with moja_auth_key as a query parameter:
POST https://webhooks.moja.cloud/{webhook_type}?moja_auth_key={your_auth_key}Requests without a valid key return 401 Unauthorized.
call_data
Section titled “call_data”Sends caller metadata ahead of the call. Moja matches it to an incoming call by caller ID, and applies any fields that correspond to custom tags you have defined.
curl -X POST 'https://webhooks.moja.cloud/call-data?moja_auth_key=YOUR_AUTH_KEY' \ -H 'Content-Type: application/json' \ -d '{ "caller_id": "+13105559876", "lead_source": "google_ads", "ad_campaign": "medicare_q1", "landing_page": "medicare-info" }'| Field | Tier | Type | Notes |
|---|---|---|---|
caller_id |
Required | string | Matched against incoming calls |
transaction_id |
Recommended | string | Idempotency key |
| Any other field | Optional | string | Stored with the request; applied as a tag if it matches a defined custom tag |
The match lookback window is configurable per webhook. The default is 15 days.
update_revenue
Section titled “update_revenue”Documented in full on the postbacks page, which is the same endpoint viewed as a conversion-reporting workflow.
update_tags and update_fields
Section titled “update_tags and update_fields”Retries and idempotency
Section titled “Retries and idempotency”Inbound. Send a stable transaction_id with each logical event. When the same organization and transaction_id arrive again after a completed attempt, Moja returns the original stored response rather than applying the request a second time. A request still in flight with that ID may return 409 Conflict — wait and retry with the same ID. Server-side idempotency is best effort, so make the sending system safe: one stable ID per event, and never retry the same event under a new ID.
Outbound. Your endpoint must respond within 10 seconds. Return 200 OK immediately and do the real work asynchronously. Deduplicate on [CALL_ID], since the same call can legitimately trigger several events. Failed deliveries are logged under Webhooks → Outgoing Webhook Requests.
Verify it works
Section titled “Verify it works”Outgoing:
- Use the Test Webhook button in the dashboard to send a sample payload to your endpoint.
- Confirm your endpoint returns
200 OKinside 10 seconds. - Attach the webhook to a campaign, target, or RTB formula.
- Place a test call and confirm the delivery appears under Webhooks → Outgoing Webhook Requests with a success status.
- Confirm every placeholder in your template resolved to a value, and that no raw
[TAG_NAME]strings arrived.
Incoming:
- Send a request with a known-good
moja_auth_keyand confirm you get a200with"success": true. - Send one with a bad key and confirm you get
401— this proves you are hitting the endpoint you think you are. - Confirm the effect landed on the call record in reporting, not just that the request was accepted.
Authenticating deliveries to your endpoint
Section titled “Authenticating deliveries to your endpoint”Moja does not sign outgoing webhook payloads with a shared secret. Authenticate them by putting your own credential in the custom headers of the webhook template:
{ "Authorization": "Bearer your-api-token", "X-Custom-Header": "custom-value"}Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
| No webhooks arriving | The webhook was saved but never attached to a campaign, target, or RTB formula | Attach it under Campaign → Outgoing webhooks |
| Still no webhooks after attaching | Endpoint unreachable, or your firewall is blocking Moja | Check delivery status under Webhooks → Outgoing Webhook Requests, then test your endpoint from outside your network |
Payload contains literal [CONVERTED] |
That tag is not populated for the event that fired | Move the tag to a later event such as call.completed, or handle placeholder strings in your receiver |
| Placeholders never substitute | Tag name is lowercase, misspelled, or missing brackets | Use uppercase inside square brackets |
| Deliveries time out | Your endpoint does work before responding | Return 200 OK first, process asynchronously |
401 Unauthorized on an incoming webhook |
moja_auth_key missing from the URL, or invalid |
Copy the exact URL from Webhooks → Incoming; header auth is not supported |
| Duplicate processing on your side | Several events fired for one call, or a retry | Deduplicate on [CALL_ID], and send a stable transaction_id on inbound requests |

