For developers
Connect your CRM
Every account already includes Prospector CRM — this 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 (included, 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_xxxxxxxxxxxxxxxxxxxxX-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://contact-dynamite-d73mzui8k-thunderbird-agency.vercel.app/api/v1/sends \
-H "Authorization: Bearer $CD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"videoId": "abc123",
"channel": "email",
"contact": {
"name": "Dana Reyes",
"email": "dana@example.com",
"phone": "+15555550147"
},
"message": "Quick update on the Maple St listing.",
"pathways": [
{ "key": "book_call" },
{ "key": "see_listing", "url": "https://…/123-maple" }
]
}'Returns 201 with the watch URL when delivery succeeded, or 202 when the send was recorded but no sender is configured — use the returned watchUrl to deliver it yourself.
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://contact-dynamite-d73mzui8k-thunderbird-agency.vercel.app/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://contact-dynamite-d73mzui8k-thunderbird-agency.vercel.app/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://contact-dynamite-d73mzui8k-thunderbird-agency.vercel.app/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://contact-dynamite-d73mzui8k-thunderbird-agency.vercel.app/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.
| Event | Fires when |
|---|---|
| video.sent | A video is delivered to a contact |
| video.opened | The email is opened, or the watch page is loaded |
| video.played | The contact starts watching |
| video.progress | A 25 / 50 / 75% milestone is passed |
| video.completed | The contact watches to the end |
| pathway.tapped | A reply button is tapped |
| reply.received | The contact records a video reply |
| contact.synced | A 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://contact-dynamite-d73mzui8k-thunderbird-agency.vercel.app/v/tok_…"
},
"data": { "pathwayKey": "book_call", "pathwayLabel": "Book a 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
| 400 | Malformed request — the body explains what's missing |
| 401 | Missing, revoked, or unrecognised API key |
| 402 | The account's subscription or Auto plan is inactive |
| 404 | No such video or contact on this account |
| 409 | The video is still processing — retry shortly |