How to Trigger Notifications from Deal Events Using Unified's CRM API
February 2, 2026
Notifications based on deal activity sound straightforward. A call happens. A meeting is scheduled. A note is added. Send a message to Slack or email the account owner.
In practice, this only works if you have a reliable way to detect what actually changed on a deal.
CRMs don't represent activity the same way. Calls, meetings, emails, tasks, and form submissions all live as different objects with different timestamps and payloads. Some CRMs attach activity directly to deals. Others attach it to contacts or companies and infer deal context later. Even the idea of 'new activity' depends on which timestamp you look at.
If you don't handle that carefully, notifications become noisy or incomplete. You miss important events, or you notify on the same event multiple times.
Unified's CRM API gives you a normalized Event object that represents deal-related activity across CRMs. Instead of reacting to vendor-specific activity models, you can poll a single event stream, filter by deal, and trigger notifications based on documented event types and fields.
This guide shows how to trigger notifications from deal events using Unified's CRM API in TypeScript. It uses polling with incremental checkpoints, event type filtering, and deal context lookup. No webhooks, no UI assumptions, and no undocumented behavior.
Prerequisites
- Node.js v18+
- A Unified account with a CRM integration enabled
- Your Unified API key
- A customer CRM
connectionId
Step 1: Set up your project
mkdir crm-deal-event-notifications
cd crm-deal-event-notifications
npm init -y
npm install @unified-api/typescript-sdk dotenv
Create a .env file:
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_CRM=your_customer_crm_connection_id
Step 2: Initialize the SDK
import "dotenv/config";
import { UnifiedTo } from "@unified-api/typescript-sdk";
const { UNIFIED_API_KEY, CONNECTION_CRM } = process.env;
if (!UNIFIED_API_KEY) throw new Error("Missing UNIFIED_API_KEY");
if (!CONNECTION_CRM) throw new Error("Missing CONNECTION_CRM");
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY },
});
const connectionId = CONNECTION_CRM;
Step 3: Understand CRM events and what they represent
Unified normalizes CRM activity into a single Event object. Every event represents an activity or engagement and is always associated with at least one deal, contact, or company.
Event types
Each event has a type field with one of the following values:
NOTEEMAILTASKMEETINGCALLMARKETING_EMAILFORMPAGE_VIEW
The type determines which nested object is present on the event payload.
Examples:
NOTE→event.noteCALL→event.callMEETING→event.meetingTASK→event.taskEMAIL→event.email
Relationship fields
Events can reference multiple entities:
deal_ids[]company_ids[]contact_ids[]lead_ids[]
For deal-based notifications, deal_ids[] is the primary join surface.
Timestamps
Events include two timestamps:
created_at— when the event was createdupdated_at— when the event was last updated
The list endpoint supports incremental filtering using updated_gte, which filters on updated_at. This is the safest checkpoint to use for polling.
Step 4: Decide which events should trigger notifications
Before writing code, decide what actually matters.
Common examples:
- Notify when a CALL is logged on a deal
- Notify when a MEETING is scheduled
- Notify when a NOTE is added
- Ignore passive events like
PAGE_VIEW
Because type is a documented enum, you can filter explicitly without guessing.
Step 5: Poll deal events incrementally
Unified does not document CRM event webhooks, so notifications should be triggered using polling.
The event list endpoint supports:
- pagination via
limitandoffset - filtering via
updated_gte - filtering by
deal_id - filtering by
type
type Sort = "name" | "updated_at" | "created_at";
type Order = "asc" | "desc";
async function fetchDealEvents(opts: {
dealId: string;
updated_gte?: string;
types?: string[];
pageSize?: number;
}) {
const pageSize = opts.pageSize ?? 100;
let offset = 0;
const out: any[] = [];
while (true) {
const page = await sdk.crm.listCrmEvents({
connectionId,
limit: pageSize,
offset,
updated_gte: opts.updated_gte ?? "",
deal_id: opts.dealId,
type: opts.types && opts.types.length === 1 ? opts.types[0] : "",
sort: "updated_at",
order: "asc",
});
if (!page || page.length === 0) break;
out.push(...page);
if (page.length < pageSize) break;
offset += pageSize;
}
return out;
}
Notes:
- Pagination stops when returned results are fewer than
limit typefiltering is optional but recommended to reduce noiseupdated_gteshould be stored and reused between runs
Step 6: Deduplicate events safely
Unified does not document idempotency or ordering guarantees for events. The only stable identifier is event.id.
To avoid duplicate notifications:
- store processed
event.idvalues, or - advance your
updated_gtecheckpoint after each run
A simple in-memory example:
const processedEventIds = new Set<string>();
function isNewEvent(e: any): boolean {
if (!e?.id) return false;
if (processedEventIds.has(e.id)) return false;
processedEventIds.add(e.id);
return true;
}
Step 7: Fetch deal context for notifications
Events carry IDs, not human-readable deal context. To build a useful notification, retrieve the deal.
async function getDealContext(dealId: string) {
return sdk.crm.getCrmDeal({
connectionId,
id: dealId,
fields: [
"id",
"name",
"stage",
"pipeline",
"amount",
"currency",
"company_ids",
"contact_ids",
],
});
}
Use only fields defined in the deal data model. Avoid assuming deal status or history.
Step 8: Build notification payloads by event type
Each event type has different fields. Build messages defensively and only read fields that exist for that type.
function buildNotification(event: any, deal: any) {
const base = {
dealId: deal.id,
dealName: deal.name,
stage: deal.stage,
pipeline: deal.pipeline,
};
switch (event.type) {
case "CALL":
return {
...base,
message: `Call logged on deal "${deal.name}"`,
durationMinutes: event.call?.duration,
};
case "MEETING":
return {
...base,
message: `Meeting scheduled for deal "${deal.name}"`,
startsAt: event.meeting?.start_at,
};
case "NOTE":
return {
...base,
message: `Note added to deal "${deal.name}"`,
note: event.note?.description,
};
default:
return null;
}
}
If buildNotification returns null, skip sending.
Step 9: Send notifications from your application
Unified provides the event signal, not the delivery channel.
At this point, hand the payload to your own notification system:
function sendNotification(payload: any) {
// email, Slack, SMS, push, etc.
console.log("Notify:", payload);
}
Step 10: Put it all together
async function run(dealId: string, lastCheckpoint?: string) {
const events = await fetchDealEvents({
dealId,
updated_gte: lastCheckpoint,
types: ["CALL", "MEETING", "NOTE"],
});
for (const e of events) {
if (!isNewEvent(e)) continue;
const deal = await getDealContext(dealId);
const notification = buildNotification(e, deal);
if (notification) {
sendNotification(notification);
}
}
const newest = events.at(-1)?.updated_at;
return newest ?? lastCheckpoint;
}
Persist the returned checkpoint (updated_at) and reuse it on the next run.
Optional: Create an event after notifying
If your workflow logs follow-up actions, you can create a CRM event after sending a notification.
await sdk.crm.createCrmEvent({
connectionId,
crmEvent: {
type: "NOTE",
note: {
description: "Notification sent to account owner",
},
deal_ids: [dealId],
},
});
Event creation support varies by CRM. Verify write support in the Feature Support tab.
Summary
Using Unified's CRM API, you can trigger notifications from deal events in a consistent way across CRMs:
- Poll normalized CRM events using
updated_gte - Filter events by deal and event type
- Deduplicate using
event.id - Retrieve deal context for human-readable messages
- Trigger notifications from your own delivery system
- Optionally log follow-up events back to the CRM