SfSkills Domains Docs GitHub

Home / integration / streaming-api-and-pushtopic

streaming-api-and-pushtopic

Use when setting up real-time data streaming from Salesforce to external systems using the Streaming API (PushTopic, Generic Streaming), CometD client configuration, replay, and channel management. NOT for Platform Events — use apex/platform-events-apex or integration/platform-events-integration.

Open the skill folder on GitHub


Streaming API and PushTopic

This skill activates when an external system needs to receive Salesforce record change notifications or arbitrary event payloads in real time, using the Streaming API over a CometD (Bayeux) connection. It covers PushTopic creation and SOQL constraints, Generic Streaming channels, replay ID handling for durable subscriptions, client configuration, and troubleshooting dropped connections or missed events.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

PushTopic

A PushTopic is a Salesforce record (PushTopic sObject) that defines a SOQL query against a standard or custom object. Salesforce evaluates each DML operation against the query and fires a streaming notification if the record matches. The notification payload includes changed field values defined in the SELECT clause.

Key fields on the PushTopic record:

FieldMeaning
NameChannel name suffix. The full channel path is /topic/<Name>.
QueryThe SOQL that defines what records trigger events. Must pass SOQL streaming restrictions.
ApiVersionAPI version to use. Must be 24.0 or higher.
NotifyForOperationCreateFire when a record matching the query is created.
NotifyForOperationUpdateFire when a matching record is updated.
NotifyForOperationDeleteFire when a matching record is deleted.
NotifyForOperationUndeleteFire when a matching record is undeleted.
NotifyForFieldsWhich fields to include in the payload: All, Referenced, Select, or Where.

NotifyForFields = Referenced is the safe default: it fires when any field used in the SOQL SELECT or WHERE changes. All fires on any field change, increasing noise. Select fires only when SELECT-listed fields change.

Generic Streaming API

Generic Streaming is used when the event payload is arbitrary (not tied to a Salesforce object). A StreamingChannel record is created with a channel name in the form /u/<ChannelName>. External or Apex code publishes payloads using the PushTopic REST endpoint or via Streaming.PushTopic (deprecated). The recommended publisher is the Streaming Channel Push REST endpoint:

POST /services/data/v60.0/sobjects/StreamingChannel/<channelId>/push
Body: { "pushEvents": [{ "payload": "my payload", "userIds": [] }] }

An empty userIds array broadcasts to all subscribers. A non-empty array restricts delivery to those user IDs.

CometD and the Handshake-Subscribe Flow

The Salesforce Streaming API implements the Bayeux protocol via CometD. The client must complete this sequence:

  1. POST to /cometd/<version> with handshake message — obtains clientId.
  2. POST with connect message — establishes the long-poll connection.
  3. POST with subscribe message to /topic/<PushTopicName> or /u/<ChannelName>.
  4. Re-issue connect immediately after each server response (long-poll loop).

The Salesforce-provided EMP Connector (Java, open source on GitHub) wraps this lifecycle and handles reconnection, replay, and subscription automatically. For JavaScript, the official cometd npm package or the Lightning empApi LWC wire adapter are the recommended clients.

Replay ID and Durable Subscriptions

Every streaming event carries a monotonically increasing replayId. A client can pass a replayId value in the subscription ext object to replay events stored within the retention window:

{
  "channel": "/topic/MyTopic",
  "ext": { "replay": { "/topic/MyTopic": -1 } }
}

Replay values:

ValueBehavior
-1 (default)Receive only new events broadcast after the client subscribes. Everything published during a prior outage is skipped.
-2Receive all events still inside the retention window, then continue with new events. PushTopic, generic, and standard-volume events are retained 24 hours; high-volume platform events and change events, 72 hours.
<replayId>Replay all events after (not including) that ID, provided it is still inside the retention window.

Get these two the right way round. -1 is the default and means tip only; -2 is the catch-up value. A subscriber that reconnects on -2 believing it means "new events only" gets the full retained window replayed on every reconnect — a duplicate flood that looks like a publisher bug. A subscriber that reconnects on -1 believing it means "catch up" silently loses every event published during the outage, with no error on either side.

Clients must persist the last-seen replayId to durable storage after the event has been processed successfully, not before. Persisting first makes the subscriber at-most-once: a crash between the write and the processing advances past an event that was never handled, and it is gone. Persisting after makes it at-least-once — the same event can arrive twice — which is the platform's contract anyway, so the subscriber has to be idempotent regardless. Choose the failure mode you can defend, and note that it is duplicates, not gaps.


Common Patterns

Mode 1: Creating a PushTopic and Subscribing via CometD

When to use: An external service needs to be notified whenever a Salesforce Opportunity is updated (e.g., Stage or Amount changes).

How it works:

  1. Create the PushTopic via Apex or REST (one-time setup):
PushTopic pt = new PushTopic();
pt.Name = 'OpportunityUpdates';
pt.Query = 'SELECT Id, Name, StageName, Amount FROM Opportunity WHERE StageName != \'Closed Lost\'';
pt.ApiVersion = 60.0;
pt.NotifyForOperationCreate = true;
pt.NotifyForOperationUpdate = true;
pt.NotifyForOperationDelete = false;
pt.NotifyForOperationUndelete = false;
pt.NotifyForFields = 'Referenced';
insert pt;
  1. External client authenticates via OAuth 2.0 and obtains access_token and instance_url.
  2. Client connects to <instance_url>/cometd/60.0 and performs handshake.
  3. Client subscribes to /topic/OpportunityUpdates with a replay extension set to the last stored replayId (or -2 on first run).
  4. Client processes each incoming message and persists the replayId before acknowledging.

Why not polling: REST polling on Opportunity every N seconds burns API request quota and has latency proportional to the polling interval. Streaming delivers sub-second notifications with a persistent connection.

Mode 2: Generic Streaming for Custom Payloads

When to use: A middleware system needs to push arbitrary JSON payloads to subscribed Salesforce users or external listeners — for example, triggering a UI refresh on a custom Lightning page.

How it works:

  1. Create a StreamingChannel record:
StreamingChannel sc = new StreamingChannel();
sc.Name = '/u/WorkflowUpdates';
insert sc;
  1. Grant permission: Users must have the "Streaming API" permission (included in most standard profiles).
  2. Publisher pushes events via the Streaming Channel Push REST endpoint:
POST /services/data/v60.0/sobjects/StreamingChannel/<id>/push
Content-Type: application/json
{ "pushEvents": [{ "payload": "{\"status\":\"ready\"}", "userIds": [] }] }
  1. Subscribers follow the same CometD handshake-subscribe flow, subscribing to /u/WorkflowUpdates.

Mode 3: Troubleshooting Connection Drops and Missed Events

When to use: The external client was offline or disconnected and suspects events were missed.

How it works:

  1. Check the last persisted replayId in client-side storage.
  2. On reconnect, set the replay extension to that replayId. The server replays all events with a higher ID within the 24-hour retention window.
  3. If no replayId was persisted, set replay to -1 to receive all retained events (risk: replay storm if the backlog is large).
  4. Verify the client is re-issuing connect immediately after every server response. A gap in the long-poll loop causes the server to expire the client session (403::Unknown client), requiring a full re-handshake.
  5. Check for rate limit errors: 401::Authentication required after a session token expires. The client must re-authenticate and reconnect, not just re-subscribe.

Decision Guidance

SituationRecommended ApproachReason
Record change notifications to an external systemPushTopic Streaming APIDirect object-change binding with SOQL filter; no Apex code needed for basic cases
Arbitrary payload broadcasting to subscribersGeneric Streaming (StreamingChannel)Decoupled from sObject schema; publisher controls payload shape
Guaranteed delivery with >24 hour retentionPlatform Events (use platform-events-integration skill)Platform Events retain events 72 hours and support higher throughput allocations
Real-time UI updates inside Salesforce LightningLWC empApi wire adapter with Platform Events or PushTopicNative Lightning integration without external CometD client
High-volume (>200k events/day) or append-only auditPub/Sub API (gRPC) or Platform EventsStreaming API PushTopic throughput is limited by org streaming allocations
Replay older than 24 hoursNot possible with Streaming API — switch to Platform EventsStreaming API event retention window is fixed at 24 hours

Step-by-step instructions for an AI agent or practitioner activating this skill:

  1. Gather context — confirm the org edition, relevant objects, and current configuration state
  2. Review official sources — check the references in this skill's well-architected.md before making changes
  3. Implement or advise — apply the patterns from Core Concepts and Common Patterns sections above
  4. Validate — run the skill's checker script and verify against the Review Checklist below
  5. Document — record any deviations from standard patterns and update the template if needed

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. PushTopic SOQL bans aggregate functions and relationship traversal — SELECT COUNT(Id), Account.Name FROM Opportunity is invalid for two separate reasons: COUNT() is an aggregate and Account.Name is a parent relationship field. Only root-object fields in the SELECT list are allowed. The insert of the PushTopic will fail with a SOQL validation error if violations exist.
  2. 24-hour event retention — not 72 — Unlike Platform Events, Streaming API retains events for only 24 hours. If a subscriber is offline for more than 24 hours, those events are gone. This is a frequent surprise for teams that assume Streaming API and Platform Events behave identically.
  3. CometD client session expiry after Salesforce session timeout — The CometD connection is bound to the OAuth session. When the session token expires (default 2 hours for connected apps without refreshTokenIssuanceTime), the server returns 401::Authentication required. The client must re-authenticate (get a fresh token) and then perform a full CometD handshake before re-subscribing. Simply re-subscribing on the old clientId will fail.

Output Artifacts

ArtifactDescription
PushTopic record definitionApex snippet or REST payload for creating the PushTopic with correct SOQL and flags
CometD subscription code sketchHandshake-subscribe loop in Java (EMP Connector) or JavaScript (cometd npm)
Replay configurationSubscription ext block with the right replayId value and persistence strategy
Channel limit assessmentEstimate of concurrent clients vs. per-channel and org-wide limits
Troubleshooting checklistOrdered diagnostic steps for missed events, dropped connections, and auth failures

Package files