# How to Send Pipedrive Won-Deal Alerts to a Shared Room

> Verify one Pipedrive v2 deal transition and send fixed shared-phone copy without forwarding deal, contact, company, or value data.

- Published: 2026-11-07
- Author: PingRoom
- Category: Automation
- Topics: Pipedrive deal won notifications, Pipedrive webhook push notifications, Pipedrive won deal alert, shared CRM notifications
- Canonical: https://pingroom.io/blog/pipedrive-deal-won-push-notifications
- Cover image: https://pingroom.io/blog/pipedrive-deal-won-push-notifications-cover.webp
- Cover description: Two PingRoom avatars move a closed clay folio through a small brass gate while Pingo watches beside a shut ledger.

**To send one Pipedrive won-deal alert to a shared group of phones, create a Pipedrive v2 webhook for `change.deal`, protect the relay with HTTP Basic auth, accept only a real `open` or `lost` → `won` transition, then queue fixed copy for a private PingRoom Room.**

Pipedrive remains the deal record. The Room receives no deal title, person, organization, value, currency, owner, note, stage, or CRM link.

This is a webhook recipe, not a native Pipedrive integration. The destination Room's owner needs **PingRoom Pro**. You also need permission to create a Pipedrive webhook, a public HTTPS relay, a durable queue, and server-side secret storage.

## Use Pipedrive or Slack first when they fit

Pipedrive already has [notifications for deal and contact updates](https://support.pipedrive.com/en/article/notifications). Appropriate Pipedrive users can keep the record, details, and next action in the CRM. For a team already working in Slack, Pipedrive's [Dealbot for Slack](https://www.pipedrive.com/en/marketplace/app/dealbot-for-slack/41654835104d703b) can post closed-deal updates to a selected channel.

| What people need                                                    | Better starting point                        |
| ------------------------------------------------------------------- | -------------------------------------------- |
| Deal details, history, owner, value, or next action                 | Pipedrive                                    |
| Deal updates for suitable Pipedrive users                           | Pipedrive notification settings              |
| Closed-deal posts for a team already working in Slack               | Dealbot for Slack                            |
| One terse signal for a stable shared group outside those user lists | Pipedrive webhook → relay → PingRoom Room    |
| Sales handoff, assignment, audit, or onboarding workflow            | CRM and the team's operations or task system |

PingRoom fits the fourth row. A won alert says only that the agreed CRM transition occurred. It does not assign the onboarding work, prove that payment arrived, or replace the record the team must open next.

## Define one fixed signal

Use reviewed server-side copy:

> **Pipedrive deal won**
>
> One selected Pipedrive deal moved to won.

If the recipients would immediately need the customer name or amount, they need authorized Pipedrive access or a different workflow. A lock-screen Ping should not become a second CRM.

Create a private Room for the stable group that needs this signal. Under **Settings → Connections → From another app**, add an incoming webhook and store its complete URL as `PINGROOM_WEBHOOK_URL` in your relay's secret store. The [incoming-webhook guide](/blog/webhook-to-push-notification) covers the Room side; the [webhook reference](/documents/webhooks) carries the current fields, limits, cooldown, and error contract.

## Create one Pipedrive v2 webhook

Pipedrive's [current v2 guide](https://pipedrive.readme.io/docs/guide-for-webhooks-v2) lets a regular user create webhooks under their own permission level; an admin can manage webhooks for other users. Pipedrive limits each user to 40 webhooks.

In Pipedrive, open **Settings → Tools and apps → Webhooks**. Create a webhook with:

- event action: `change`;
- event object: `deal`;
- version: `2.0`;
- subscription URL: your public HTTPS relay, with no redirect;
- HTTP auth username and password: long, random values stored only in Pipedrive and your relay.

Store these values on the server:

- `PIPEDRIVE_BASIC_AUTH_USERNAME` and `PIPEDRIVE_BASIC_AUTH_PASSWORD`;
- `PIPEDRIVE_COMPANY_ID`;
- `PIPEDRIVE_WEBHOOK_ID`;
- `PIPEDRIVE_COMPANY_HOST`, such as `example.pipedrive.com`;
- `PINGROOM_WEBHOOK_URL`.

Use a permission-level user who can see the intended deals. Pipedrive does not send webhook events for objects that user cannot access.

## Verify before reading the CRM event

Pipedrive v2 supports HTTP Basic auth for the subscription URL. Compare the complete `Authorization` value in constant time before parsing the JSON body. Then require the exact company, host, webhook, version, action, and entity you configured.

The important transition boundary is in two places:

- `data.status` is the current value and must be `won`;
- `previous` contains only changed fields, so it must contain `status` with the prior value `open` or `lost`.

That second check prevents an unrelated edit to an already-won deal from creating another won alert.

```js
import { createHash, timingSafeEqual } from "node:crypto";

function constantTimeEqual(left, right) {
  const a = Buffer.from(left ?? "", "utf8");
  const b = Buffer.from(right ?? "", "utf8");
  return a.length === b.length && timingSafeEqual(a, b);
}

function validBasicAuth(header, username, password) {
  const expected = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
  return constantTimeEqual(header, expected);
}

function selectedWonTransition(event, env) {
  const meta = event?.meta;
  const data = event?.data;
  const previous = event?.previous;

  return (
    meta?.action === "change" &&
    meta?.entity === "deal" &&
    meta?.type === "general" &&
    meta?.version === "2.0" &&
    String(meta.company_id) === env.PIPEDRIVE_COMPANY_ID &&
    String(meta.webhook_id) === env.PIPEDRIVE_WEBHOOK_ID &&
    meta.host === env.PIPEDRIVE_COMPANY_HOST &&
    String(data?.id) === String(meta.entity_id) &&
    data?.status === "won" &&
    previous !== null &&
    typeof previous === "object" &&
    Object.hasOwn(previous, "status") &&
    ["open", "lost"].includes(previous.status)
  );
}

function eventKey(companyId, eventId) {
  const digest = createHash("sha256")
    .update(`${companyId}:${eventId}`)
    .digest("hex");
  return `pipedrive-won:${digest}`;
}
```

The production receiver should also reject an empty or oversized body, require `application/json`, validate Pipedrive's UUID-shaped `meta.id`, and require the numeric deal identity in `meta.entity_id` to match `data.id`.

## Queue identity, not CRM content

Pipedrive supplies one event ID as `meta.id`. Derive an opaque key from that ID and the configured company ID. Store a body digest beside it so an exact retry replays safely while changed content under the same event identity becomes a conflict.

The durable queue job needs only this:

```js
const key = eventKey(env.PIPEDRIVE_COMPANY_ID, event.meta.id);
const bodyDigest = createHash("sha256").update(rawBody).digest("hex");

await queue.enqueueOnce(key, bodyDigest, {
  eventKey: key,
  request: {
    action: null,
    title: "Pipedrive deal won",
    message: "One selected Pipedrive deal moved to won.",
    correlation_id: key,
  },
});
```

Do not put the Pipedrive payload in that job. Queue persistence should happen before the relay returns `2xx` to Pipedrive. Pipedrive retries a failed or timed-out webhook after 3, 30, and 150 seconds; repeated receiver failures can also ban or remove the webhook. A durable enqueue is the boundary between accepting the source event and attempting the phone signal.

![Two PingRoom avatars check one sealed event token in a queue tray while Pingo waits beside an identical retry held behind a wooden stop.](/blog/pipedrive-deal-won-push-notifications-1.webp)

[Pingo the Pigeon](/Pingo), PingRoom's fictional Chief Marketing Officer, appears beside the closed folio in these illustrations. He cannot inspect CRM records, access the private Room, declare a deal won, or send the Ping. The sales team owns the status change; deterministic relay code owns the filter.

## Let one consumer send the fixed Ping

One queue consumer posts the reviewed body and reuses the same key for PingRoom idempotency:

```js
const response = await fetch(env.PINGROOM_WEBHOOK_URL, {
  method: "POST",
  redirect: "manual",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": job.eventKey,
  },
  body: JSON.stringify(job.request),
});

if (response.status !== 200 || (await response.json())?.success !== true) {
  throw new Error("retry queued Ping");
}
```

Retry PingRoom throttles, server errors, network failures, and incomplete outcomes from the queue. Reuse the same key and identical request body. A new key is a new Ping, not a repair for a failed attempt.

## Rehearse the boundary with a disposable deal

Before enabling ordinary CRM traffic, use a test Room and a disposable deal with generic fields:

1. Move the deal from `open` to `won`; exactly one fixed Ping should be queued.
2. Replay the same v2 body and event ID; no second queue job or Ping should appear.
3. Change the body while keeping the event ID; the relay should reject the conflict.
4. Edit a deal that was already `won`; no new won alert should be queued.
5. Send another action, entity, company, host, webhook ID, or invalid Basic credential; the receiver should reject or ignore it as designed.
6. Make queue persistence fail; the relay should not acknowledge the source event as accepted.
7. Make PingRoom return a throttle or server error; the queued job should remain retryable.
8. Lock a test phone and confirm the preview contains no deal, person, company, value, currency, owner, note, stage, or CRM link.

The companion relay for this article passed **51 deterministic assertions** with Pipedrive v2-shaped fixtures, a Basic-authenticated receiver, an idempotent in-memory queue, and a local mock of PingRoom's documented webhook response. No live Pipedrive account, public relay, PingRoom Room, CRM data, or phone delivery was tested.

An accepted PingRoom response means the Ping was created and entered PingRoom's normal delivery path. It does not prove that a phone displayed it, that a person saw it, that onboarding began, or that the deal produced revenue. Notification permission, connectivity, Focus modes, Room settings, and operating-system behavior still matter.

If the shared-Room path is the right fit, [create the private Room](/app/rooms), deploy the relay with a durable queue, and move one disposable deal through the exact transition before connecting real work.

_The workflow and CRM data in this guide are illustrative. The two editorial illustrations were created with AI using approved PingRoom character references and reviewed by the PingRoom team._
