# How to Send Salesforce Closed-Won Alerts to a Shared Room

> Use a record-triggered Salesforce Flow, a small privacy-minimizing relay, and a PingRoom incoming webhook to notify the right phones when an opportunity first becomes Closed Won.

- Published: 2026-11-08
- Author: PingRoom
- Category: Integrations
- Topics: Salesforce closed won notification, Salesforce Flow HTTP Callout, webhook push notifications, automation notifications to phone
- Canonical: https://pingroom.io/blog/salesforce-closed-won-push-notifications
- Cover image: https://pingroom.io/blog/salesforce-closed-won-push-notifications-cover.webp
- Cover description: Two PingRoom teammates and Pingo review a brass stage gate with one marker moved into the completed position.

# How to Send Salesforce Closed-Won Alerts to a Shared Room

When one selected Salesforce opportunity first enters **Closed Won**, an after-save record-triggered Flow can send the opportunity ID and change time to a small authenticated relay. The relay turns that event into fixed, privacy-minimizing copy and posts it to a private PingRoom Room. The people in that Room receive the PingRoom notification on their phones according to their device and notification settings.

This is a custom webhook recipe, not a native Salesforce integration. Start with [Salesforce custom notifications](https://help.salesforce.com/s/articleView?id=platform.automate_flow_build_example_send_custom_notification.htm&language=en_US&type=5) when every recipient is already an appropriate Salesforce user. Use the relay pattern when a small, accountable group needs the event in a shared Room without putting CRM details in the alert. The Room owner needs PingRoom Pro to create the incoming webhook.

## Pick the smallest useful route

| Need                                                                 | Better route                                                                    |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Notify Salesforce users inside Salesforce                            | Salesforce custom notification                                                  |
| Put one bounded event in a private shared Room                       | Flow → authenticated relay → PingRoom incoming webhook                          |
| Send the amount, customer, contact, or owner in the phone alert      | Do not use this recipe; keep those details in Salesforce                        |
| Trigger payment, onboarding, fulfillment, or another business action | Use an auditable system of record and workflow engine, not a phone notification |

The Room alert should answer one question: **did this selected opportunity enter Closed Won?** It should not become a second CRM record.

## The tested setup

The worked example uses this path:

1. Salesforce updates an Opportunity.
2. A record-triggered Flow runs **after save**.
3. The entry condition checks `StageName = Closed Won` and runs only when the record is updated to meet that condition.
4. An HTTP Callout sends only `opportunity_id` and `changed_at` to your relay.
5. The relay authenticates the request, validates the two fields, and derives a stable event key.
6. A durable queue accepts that event once and sends fixed copy to a PingRoom incoming webhook.
7. A later retry reuses the same PingRoom idempotency key.

Salesforce documents the same “updated to meet the condition” pattern in its [Closed Won Flow example](https://help.salesforce.com/s/articleView?id=000389981&language=en_US&type=1). It prevents ordinary edits to an already-won opportunity from producing a fresh alert. If someone later reopens and wins the opportunity again, the later change time becomes a new event.

## Configure the Salesforce side

Create a Named Credential for the relay host, then put the shared relay secret in a custom header such as `x-salesforce-relay-key`. Salesforce recommends using a [Named Credential](https://help.salesforce.com/s/articleView?id=sf.external_services_define_named_credential.htm&language=en_US&type=5) for authenticated callouts instead of embedding credentials in Flow data.

Create an Opportunity record-triggered Flow with:

- Trigger: a record is updated.
- Condition: `StageName` equals `Closed Won`.
- When to run: only when the record is updated to meet the condition requirements.
- Optimize for: actions and related records, so the Flow runs after save.
- Callout body: the Opportunity ID and the Flow current date-time value.
- Fault path: record the failure for an administrator; do not pretend the callout succeeded.

Use Salesforce's [HTTP Callout action](https://help.salesforce.com/s/articleView?id=flow_http_callout.htm&language=en_US&type=5) to send this deliberately narrow body:

```json
{
  "opportunity_id": "{!$Record.Id}",
  "changed_at": "{!$Flow.CurrentDateTime}"
}
```

Do not include the opportunity name, amount, account, contact, owner, email address, notes, or free-form fields. Salesforce says HTTP Callout requests use the Named Credential and generated schema; its [callout considerations](https://help.salesforce.com/s/articleView?id=sf.flow_http_callout_considerations.htm&language=en_US&type=5) also note that HTTP responses at 300 or above are Flow errors. Connect the fault path accordingly.

## Put a relay between Salesforce and PingRoom

The [PingRoom incoming webhook URL](/documents/webhooks) is a Room credential. Do not paste it directly into a broadly editable Flow or return it in a response. Store it as a server-side secret in the relay. If this is your first relay, the broader [webhook-to-push guide](/blog/webhook-to-push-notification) explains the request contract and credential boundary.

The relay accepts exactly two fields, converts the change time to a canonical ISO timestamp, and hashes this identity:

```js
const eventIdentity = `${opportunityId}:${changedAt.toISOString()}`;
const eventKey = `salesforce-won:${sha256(eventIdentity)}`;
```

It then queues fixed copy rather than forwarding Salesforce data:

```js
const job = {
  eventKey,
  request: {
    action: null,
    title: "Salesforce opportunity won",
    message:
      "One selected opportunity entered Closed Won. Open Salesforce for details.",
    correlation_id: eventKey,
  },
};
```

This separation matters. Acceptance and delivery are different states: the relay returns `202 Accepted` only after the durable queue owns the event. A worker then posts to PingRoom with the same event key in `Idempotency-Key`, and treats delivery as complete only when PingRoom returns HTTP 200 with `{ "success": true }`. Rate limits, server errors, and unsuccessful bodies remain retryable.

The full, runnable relay and deterministic verifier are preserved with this article's production record. They cover authentication, strict schema rejection, body-size bounds, exact replay, a later re-win, queue failure, fixed-copy minimization, stable idempotency, and downstream retry behavior.

![Two PingRoom teammates and Pingo observe one accepted event marker while a duplicate marker is stopped before the phone.](/blog/salesforce-closed-won-push-notifications-1.webp)

[Pingo](/Pingo), PingRoom's fictional CMO, is stationed beside the duplicate stop in this example. His expression is useful editorial shorthand, not evidence that a pigeon can inspect your CRM, access a private Room, or authorize a Ping. Your Salesforce administrator owns the Flow, your relay owns validation and retries, and Room members control who receives the result.

## Rehearse the transition before enabling it

Use a sandbox or test opportunity and write down the expected result before switching the Flow on:

1. Move a test opportunity from an open stage to Closed Won. Expect one queued event and one Room alert.
2. Replay the identical relay request. Expect `202` with `duplicate: true` and no second queued job.
3. Edit a description on the already-won opportunity. Expect no new Flow run because it did not newly meet the condition.
4. Reopen the opportunity, then move it to Closed Won again. Expect a new event because the change time differs.
5. Remove or alter the relay secret. Expect `401` and no queued event.
6. Add an unexpected CRM field such as `amount`. Expect `400` and no queued event.
7. Make the queue unavailable. Expect `503`, so Salesforce can expose the failure through the Flow fault path.
8. Simulate PingRoom rate limiting or a server error. Expect the worker to retain the job for retry with the same idempotency key.

The included verifier exercises 56 deterministic assertions with in-memory queue and HTTP mocks. It does not call a live Salesforce org or PingRoom Room, so complete the sandbox rehearsal with your own permissions and notification settings before production use.

## Keep the alert accountable

Name the Room for its audience, not the CRM record: for example, “Sales handoffs” rather than a customer or opportunity name. Add only the people who need the transition. Decide who owns the Salesforce fault path and who rotates both the relay secret and the PingRoom webhook if either is exposed.

The useful outcome is intentionally modest: the right small group learns that a selected stage changed without repeatedly checking Salesforce. They still open Salesforce for the authoritative details and perform any next action there.

[Create the private Room that should receive the alert](/app/rooms), then generate its incoming webhook and test the relay with a sandbox opportunity before enabling the Flow.

_Illustrations in this article were created with AI using PingRoom's approved Pingo and avatar references, then reviewed against those references._
