Skip to content
NMC Docs

Webhooks

Receive real-time event notifications from NMC, verify signatures, and handle retries safely.

Webhooks notify your server with an HTTP POST request whenever a subscribed event happens in NMC, such as a new lead arriving or a call finishing. Each event payload includes the resource as it looked when the event occurred.

Users with account access can manage webhook endpoints in the business’s Settings → Webhooks or create a webhook endpoint through the API (invited clients cannot). In the dashboard, choose Add webhook endpoint to create an endpoint and open its detail page, where you can reveal its signing secret, edit subscribed events, send a test event, and inspect deliveries.

Event When it fires
lead.created A new lead was created. Its first call may still be connected.
lead.updated A lead’s stage, closure, value, note, latest activity, or current AI review changed.
activity.created A call, text, form submission, or forwarded email was added to a lead, including follow-ups on an existing lead.
activity.updated Activity details changed, such as call completion or recording and transcript readiness.
contact.created A new contact was created.
contact.updated Contact details or the contact’s latest activity changed.
business.updated Public business settings changed, or tracking paused or resumed.

Select at least one event type; each webhook endpoint receives only the events you subscribe to. Each business can have up to three webhook endpoints by default, counting stopped endpoints and those created through the API.

Terminal window
curl --fail-with-body --request POST \
--header "Authorization: Bearer $NMC_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"url":"https://receiver.example.com/nmc",
"event_types":["lead.created","lead.updated","activity.updated"]
}' \
https://api.nomissedconversions.com/v1/webhook_endpoints

Replace the example URL with your public HTTPS endpoint on port 443. NMC does not follow HTTP redirects or deliver to private network addresses. Store both the returned webhook endpoint id and signing_secret in your server’s secret configuration.

NMC signs the raw request body using the Standard Webhooks specification. Incoming webhook requests don’t use an NMC API key; instead, verify these three headers against your webhook endpoint’s signing_secret:

  • webhook-id: Unique identifier for the event (use this to deduplicate deliveries).
  • webhook-timestamp: Unix timestamp for this delivery attempt.
  • webhook-signature: Versioned HMAC signature.

Install the official Standard Webhooks library (standardwebhooks) in your receiver. This helper verifies a standard Web Request and returns the parsed payload:

import { Webhook } from "standardwebhooks"
export async function verifyNmcWebhook(request, signingSecret) {
const rawBody = await request.text()
const verifier = new Webhook(signingSecret)
return verifier.verify(rawBody, {
"webhook-id": request.headers.get("webhook-id") ?? "",
"webhook-timestamp": request.headers.get("webhook-timestamp") ?? "",
"webhook-signature": request.headers.get("webhook-signature") ?? "",
})
}

Always read the raw request body before any JSON middleware parses or reformats it. Reject requests that fail verification without processing their contents, leave the library’s timestamp tolerance check enabled, and keep your server clock synced. Once verified, save the event before running downstream work.

After verifying the signature, validate the payload and write the event (along with any queued background job) to persistent storage that survives a server restart. Enforce a unique database constraint on the event ID (webhook-id / payload id) so two concurrent deliveries of the same event can’t run the same work twice. Return a 2xx response once saved, and return 2xx right away if you’ve already stored that event ID.

Webhook deliveries can arrive more than once or out of order. If your application keeps a local copy of NMC records, fetch the current record from the API when processing each event so a delayed event never overwrites fresher data.

Every webhook payload includes id, type, api_version, occurred_at, is_test, data, related, and business:

  • Changed fields: data.object holds the resource that triggered the event. data.changed_fields lists the public field paths that changed, such as stage or tracking.state ([] on *.created events, and a single path when an array changes or an object appears or disappears).
  • Activities vs. parent records: Updating an activity doesn’t necessarily emit a separate lead.updated or contact.updated event, even when those records are included in related. Subscribe to activity.created and activity.updated if you want every call, text, form, and email, including repeat calls on an open lead.
  • Earliest activity (is_first_activity): is_first_activity marks the lead’s earliest activity by occurrence time. If an activity with an earlier timestamp is attached to the lead later, is_first_activity moves to that earlier activity: NMC sends lead.updated with first_activity_id in changed_fields, without sending activity.updated for the activity that was previously first.
  • Contact matching: NMC matches each activity to a contact by phone number first and email address second, and each email address belongs to at most one contact. An email-only activity can therefore match a contact who already has a phone number, and a later activity can add a phone number or email address to an existing contact (emitting contact.updated).

Open any event link in the table above for its complete schema and field descriptions. For example, this business.updated test event shows a business name change (lead.*, activity.*, and contact.* events carry those resources in data.object instead):

{
"id": "01900000-0000-7000-8000-000000000010",
"type": "business.updated",
"api_version": "2026-10-01",
"occurred_at": "2026-09-20T15:30:00Z",
"is_test": true,
"data": {
"object": {
"id": "01900000-0000-7000-8000-000000000020",
"name": "Example Heating",
"website_url": "https://www.example.com",
"timezone": "America/Vancouver",
"currency_code": "CAD",
"tracking": { "state": "active", "since": "2026-09-01T00:00:00Z" }
},
"changed_fields": ["name"]
},
"related": {},
"business": {
"id": "01900000-0000-7000-8000-000000000020",
"name": "Example Heating",
"website_url": "https://www.example.com",
"timezone": "America/Vancouver",
"currency_code": "CAD",
"tracking": { "state": "active", "since": "2026-09-01T00:00:00Z" }
}
}
  • Timeouts and retry window: Your endpoint has 15 seconds to return any 2xx response. NMC retries non-2xx responses and timeouts with increasing delays, and marks the delivery Failed after roughly 3 days without a 2xx response.
  • Stopping an endpoint (410 or manual toggle): Returning 410 stops the webhook endpoint immediately, cancels its remaining unsent deliveries, and emails the account. Turning a webhook endpoint off in the dashboard also stops it and cancels unsent deliveries. While stopped, NMC creates no deliveries for that endpoint, so events from that period are never sent. After turning it back on, you can resend any deliveries that were canceled when it stopped.
  • Inspecting deliveries: Open the webhook endpoint from Settings → Webhooks and check Deliveries for Failed or in-flight attempts (check your server logs before resending, since your server may have received a delivery that timed out). To monitor endpoint status from code, call GET /webhook_endpoints.
  • Test events: Use Send test on the webhook endpoint’s page to send a test event for a chosen event type. Test payloads set is_test: true; skip real customer notifications or live side effects when is_test is true. Send test and Resend share a limit of 10 actions per minute per business.
  • Retries, resends, and secret rotation: Automatic retries and manual Resend actions keep the original event id and JSON body, while sending to the webhook endpoint’s current URL and signing with its current signing_secret and a fresh timestamp. Coordinate secret changes with your receiver, and never use webhook-timestamp as your deduplication key.
  • Deleting an endpoint: Removing a webhook endpoint deletes its delivery records, though a request already in flight may still arrive. Keep the webhook endpoint id if your integration needs to remove it later.