For developers

Connect your CRM

Prospector CRM, Follow Up Boss and Wise Agent connect from Settings → CRM. This page is for connecting something else, or driving Contact Dynamite from your own systems. Two directions, use either or both. Push: call the REST API to send a video, sync a contact, or fire an automation. Pull: point a webhook at your own service and receive every send, watch, button tap and video reply as it happens.

Built-in connectors: Prospector CRM (sends and logs), Follow Up Boss and Wise Agent(log activity; delivery runs through us). BoldTrail/kvCORE and KW Command need a vendor agreement we don't hold yet — use the webhook below meanwhile.

Authentication

Create a key in Settings → CRM. Keys are shown once and scoped to a single account. Pass it as a bearer token:

Authorization: Bearer cd_live_xxxxxxxxxxxxxxxxxxxx

X-API-Key: <key> works too, if that's easier for your platform.

Send a video

The endpoint that makes “send a Contact Dynamite video” a step in any CRM workflow. Delivers the email or text and starts tracking immediately.

POST/api/v1/sends

curl -X POST https://contactdynamite.com/api/v1/sends \
  -H "Authorization: Bearer $CD_API_KEY" \
  -H "Idempotency-Key: REPLACE_WITH_A_UNIQUE_MESSAGE_UUID" \
  -H "Content-Type: application/json" \
  -d '{
    "videoId": "abc123",
    "channel": "email",
    "consentConfirmed": true,
    "contact": {
      "name": "Dana Reyes",
      "email": "dana@example.com",
      "phone": "+15555550147"
    },
    "headline": "{agent} recorded a video for you",
    "subject": "A quick video for you, {first_name}",
    "message": "Hi {first_name}, quick update on the Maple St listing.",
    "pathways": [
      { "key": "book_call" },
      { "key": "see_listing", "url": "https://…/123-maple" },
      { "key": "custom_1", "label": "Get pre-approved", "url": "https://…" }
    ]
  }'

Reply buttons: book_call needs a scheduling link on your profile, and call_me a phone or text-back number it can dial. Up to two of your own buttons, custom_1 and custom_2, each with a label of at most 32 characters and an https url.

Optional wording: headline (at most 160 characters, shown at the top of the email; default {agent} recorded a video for you), subject (at most 200 characters; defaults to the headline) and message. All three accept the merge fields {first_name}, {agent}, {agent_first} and {brand}, filled in for each contact (a missing first name reads “there”). Any other field in braces is refused with a 400.

Optional look: themepicks one of the looks the video's brand offers (owner and team accounts); leave it out to use the look the video was recorded with. A look the brand doesn't offer is refused with a 400. A brand that shows each sender's own NMLS line refuses sends until your NMLS ID is saved in Settings.

A video without a brand uses the classic Contact Dynamite email, so a theme on it is refused with a 400.

Use a new Idempotency-Key for each intended message and reuse it for retries. Send only to known contacts who agreed to receive email. Each recipient uses one email allowance unit. The returned state distinguishes provider acceptance from failed or unconfirmed sends; acceptance does not prove inbox delivery. Automated SMS is unavailable.

Videos

List the account's library to pick one to send.

GET/api/v1/videos?status=ready&limit=50

{
  "videos": [
    {
      "id": "abc123",
      "title": "Maple St — just listed",
      "status": "ready",
      "durationSeconds": 42,
      "shareUrl": "https://contactdynamite.com/v/abc123",
      "createdAt": "2026-08-20T17:04:11.000Z"
    }
  ]
}

Contacts

Push contacts in from your CRM. Idempotent on email, then phone — safe to re-sync on every change.

POST/api/v1/contacts

GET/api/v1/contacts?limit=100

curl -X POST https://contactdynamite.com/api/v1/contacts \
  -H "Authorization: Bearer $CD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dana Reyes",
    "email": "dana@example.com",
    "crmContactId": "your-crm-id-here"
  }'
{
  "contact": {
    "id": "…",
    "name": "Dana Reyes",
    "email": "dana@example.com",
    "phone": null,
    "label": null,
    "crmContactId": "your-crm-id-here",
    "createdAt": "2026-08-21T13:58:02.000Z"
  }
}

Pass crmContactId to keep your own identifier on the record, so engagement webhooks come back with an id you already know. A new contact returns 201, a matched one 200.

Engagement

For CRMs that would rather poll than receive webhooks. Filter with since to pull incrementally.

GET/api/v1/engagement?since=2026-08-01T00:00:00Z

{
  "sends": [
    {
      "sendId": "…",
      "watchUrl": "https://contactdynamite.com/v/tok_…",
      "status": "watched",
      "contact": { "email": "dana@example.com", "crmContactId": "…" },
      "maxWatchPercent": 100,
      "openedAt": "2026-08-21T14:02:00.000Z",
      "firstWatchedAt": "2026-08-21T14:02:19.000Z"
    }
  ]
}

Automation triggers

Fire a Dynamite Auto rule from your CRM — a new lead, a birthday, a price drop. Requires the Dynamite Auto plan.

POST/api/v1/triggers

curl -X POST https://contactdynamite.com/api/v1/triggers \
  -H "Authorization: Bearer $CD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "trigger": "new_lead",
    "contact": { "name": "Dana Reyes", "email": "dana@example.com" },
    "merge": { "area": "Phoenix", "detail": "3bd ranch under $600k" }
  }'

Triggers: new_lead, birthday, price_drop, market_update, campaign.

Engagement webhooks

Set a webhook URL in Settings → CRM and every event is POSTed to it as JSON. This is how a CRM we don't integrate with natively — BoldTrail, KW Command, anything in-house — still gets a full timeline.

EventFires when
video.sentA video is delivered to a contact
video.openedThe email is opened, or the watch page is loaded
video.playedThe contact starts watching
video.progressA 25 / 50 / 75% milestone is passed
video.completedThe contact watches to the end
pathway.tappedA reply button is tapped
reply.receivedThe contact records a video reply
contact.syncedA contact is created or updated
POST https://your-crm.example.com/hooks/dynamite
X-Dynamite-Event: pathway.tapped
X-Dynamite-Timestamp: 1787000000
X-Dynamite-Signature: sha256=…

{
  "event": "pathway.tapped",
  "occurredAt": "2026-08-21T14:03:44.000Z",
  "contact": {
    "name": "Dana Reyes",
    "email": "dana@example.com",
    "crmContactId": "your-crm-id-here"
  },
  "video": {
    "id": "abc123",
    "title": "Maple St — just listed",
    "watchUrl": "https://contactdynamite.com/v/tok_…"
  },
  "data": { "pathwayKey": "book_call", "pathwayLabel": "Book a call", "pathwaySlug": "book-call" }
}

Verifying a webhook

Each delivery is signed with the shared secret you set. Recompute the HMAC over <timestamp>.<raw body> and compare in constant time. Reject anything older than five minutes.

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(req, rawBody, secret) {
  const timestamp = req.headers["x-dynamite-timestamp"];
  const signature = req.headers["x-dynamite-signature"];
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected =
    "sha256=" + createHmac("sha256", secret)
      .update(`${timestamp}.${rawBody}`)
      .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}

Respond 2xx to acknowledge. Anything else is retried with backoff — roughly 1 minute, 5, 15, 1 hour, then 6 — so a brief outage on your side delays the sync rather than losing it.

Errors

400Malformed request — the body explains what's missing
401Missing, revoked, or unrecognised API key
402The account's subscription or Auto plan is inactive
404No such video or contact on this account
409The video is still processing — retry shortly