---
title: "How to Trigger Notifications from Deal Events Using Unified's CRM API"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_trigger_notifications_from_deal_events_using_unified_crm_api-icon.png
date: 2026-02-02T17:06:00.000Z
tag: Product
description: "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..."
url: "https://unified.to/blog/how_to_trigger_notifications_from_deal_events_using_unified_crm_api"
---

# 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](/crm) 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](/blog/replacing_polling_with_unified_webhooks_and_virtual_streams), no UI assumptions, and no undocumented behavior.


### Prerequisites

- Node.js v18+
- A Unified account with a CRM integration enabled
- Your [Unified API](/technology) key
- A customer CRM `connectionId`

## Step 1: Set up your project


```bash
mkdir crm-deal-event-notifications
cd crm-deal-event-notifications
npm init -y
npm install @unified-api/typescript-sdk dotenv
```


Create a `.env` file:


```bash
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_CRM=your_customer_crm_connection_id
```


## Step 2: Initialize the SDK


```typescript
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:

- `NOTE`
- `EMAIL`
- `TASK`
- `MEETING`
- `CALL`
- `MARKETING_EMAIL`
- `FORM`
- `PAGE_VIEW`

The `type` determines which nested object is present on the event payload.


Examples:

- `NOTE` → `event.note`
- `CALL` → `event.call`
- `MEETING` → `event.meeting`
- `TASK` → `event.task`
- `EMAIL` → `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 created
- `updated_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 `limit` and `offset`
- filtering via `updated_gte`
- filtering by `deal_id`
- filtering by `type`

```typescript
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`
- `type` filtering is optional but recommended to reduce noise
- `updated_gte` should 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.id` values, or
- advance your `updated_gte` checkpoint after each run

A simple in-memory example:


```typescript
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.


```typescript
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.


```typescript
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:


```typescript
function sendNotification(payload: any) {
  // email, Slack, SMS, push, etc.
  console.log("Notify:", payload);
}
```


## Step 10: Put it all together


```typescript
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.


```typescript
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

→ [Start your 30-day free trial](https://app.unified.to/login)


→ [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)