salesforce-webhooks-events
Implement Salesforce Platform Events, Change Data Capture (CDC), and Outbound Messages. Use when building real-time integrations, listening for record changes, or implementing event-driven architecture with Salesforce. Trigger with phrases like "salesforce events", "salesforce CDC", "salesforce platform events", "salesforce streaming", "salesforce outbound message", "salesforce real-time".
What this skill does
# Salesforce Webhooks & Events
## Overview
Salesforce doesn't use traditional webhooks. Instead, it offers Platform Events, Change Data Capture (CDC), and Outbound Messages for real-time data flow. All use the CometD (Bayeux) streaming protocol via jsforce.
## Prerequisites
- jsforce installed with connection configured
- Platform Events or CDC enabled in your org
- Understanding of publish/subscribe patterns
- Express.js for Outbound Message endpoints
## Event Mechanism Comparison
| Mechanism | Direction | Use Case | Retention |
|-----------|-----------|----------|-----------|
| Platform Events | Bi-directional | Custom event bus | 72 hours |
| Change Data Capture (CDC) | Salesforce → External | Record change notifications | 3 days |
| Outbound Messages | Salesforce → External | Workflow-triggered HTTP POST | Until confirmed |
| Streaming API (PushTopics) | Salesforce → External | SOQL-based subscriptions | No replay |
## Instructions
### Step 1: Subscribe to Change Data Capture (CDC)
```typescript
import jsforce from 'jsforce';
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL,
});
await conn.login(process.env.SF_USERNAME!, process.env.SF_PASSWORD! + process.env.SF_SECURITY_TOKEN!);
// Subscribe to Account changes
// CDC channel format: /data/AccountChangeEvent
const subscription = conn.streaming.topic('/data/AccountChangeEvent').subscribe((message) => {
const header = message.payload.ChangeEventHeader;
console.log('Change Type:', header.changeType); // CREATE, UPDATE, DELETE, UNDELETE
console.log('Record IDs:', header.recordIds);
console.log('Changed Fields:', header.changedFields);
console.log('User ID:', header.commitUser);
// Access changed field values
if (header.changeType === 'UPDATE') {
console.log('New values:', message.payload);
// Only changed fields are populated in the payload
}
});
// Enable CDC for objects in Setup:
// Setup > Integrations > Change Data Capture > Select Objects
```
### Step 2: Publish and Subscribe to Platform Events
```typescript
// Define a Platform Event in Salesforce:
// Setup > Platform Events > New Platform Event
// Example: Order_Status__e with fields:
// - Order_Id__c (Text)
// - Status__c (Text)
// - Amount__c (Number)
// Publish a Platform Event via API
await conn.sobject('Order_Status__e').create({
Order_Id__c: 'ORD-12345',
Status__c: 'Shipped',
Amount__c: 499.99,
});
// Subscribe to Platform Events
const eventSub = conn.streaming.topic('/event/Order_Status__e').subscribe((message) => {
console.log('Event received:', {
orderId: message.payload.Order_Id__c,
status: message.payload.Status__c,
amount: message.payload.Amount__c,
replayId: message.event.replayId,
});
});
// Use replayId to resume from a specific point (-1 = new only, -2 = all available)
conn.streaming.topic('/event/Order_Status__e', { replayId: -2 }).subscribe((message) => {
// Receives all stored events (up to 72 hours)
});
```
### Step 3: Handle Outbound Messages (SOAP-based)
```typescript
// Outbound Messages are sent by Salesforce Workflow Rules or Flows
// They are SOAP XML posts to your endpoint
import express from 'express';
import { parseString } from 'xml2js';
const app = express();
app.use(express.text({ type: 'text/xml' }));
app.post('/salesforce/outbound-message', (req, res) => {
parseString(req.body, (err, result) => {
if (err) {
console.error('XML parse error:', err);
return res.status(400).send('Invalid XML');
}
// Extract notification data
const notification = result['soapenv:Envelope']['soapenv:Body'][0]
['notifications'][0]['Notification'][0];
const sobject = notification['sObject'][0];
console.log('Record ID:', sobject['sf:Id'][0]);
console.log('Object Type:', sobject.$['xsi:type']);
// Respond with acknowledgment (required!)
res.type('text/xml').send(`<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:out="http://soap.sforce.com/2005/09/outbound">
<soapenv:Body>
<out:notificationsResponse>
<out:Ack>true</out:Ack>
</out:notificationsResponse>
</soapenv:Body>
</soapenv:Envelope>`);
});
});
```
### Step 4: Robust Event Processing
```typescript
// Idempotent event handler with replay ID tracking
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function processEvent(message: any): Promise<void> {
const replayId = message.event.replayId;
const eventKey = `sf:event:${replayId}`;
// Check if already processed (idempotency)
if (await redis.exists(eventKey)) {
console.log(`Event ${replayId} already processed, skipping`);
return;
}
try {
// Process the event
const changeType = message.payload.ChangeEventHeader?.changeType;
const recordIds = message.payload.ChangeEventHeader?.recordIds || [];
switch (changeType) {
case 'CREATE':
await handleRecordCreated(recordIds, message.payload);
break;
case 'UPDATE':
await handleRecordUpdated(recordIds, message.payload);
break;
case 'DELETE':
await handleRecordDeleted(recordIds);
break;
}
// Mark as processed with 7-day TTL
await redis.set(eventKey, '1', 'EX', 86400 * 7);
// Save replay ID for resume on restart
await redis.set('sf:last-replay-id', replayId.toString());
} catch (error) {
console.error(`Failed to process event ${replayId}:`, error);
throw error; // Let retry logic handle it
}
}
```
## Output
- CDC subscription for real-time record change notifications
- Platform Event publishing and subscribing
- Outbound Message endpoint with SOAP acknowledgment
- Idempotent event processing with replay ID tracking
## Error Handling
| Issue | Cause | Solution |
|-------|-------|----------|
| `403: CDC not enabled` | Object not selected for CDC | Setup > Change Data Capture > select objects |
| `EVENT_OR_PUSHTTOPIC_NOT_FOUND` | Platform Event doesn't exist | Create in Setup > Platform Events |
| Missed events | Client disconnected | Use `replayId` to resume from last position |
| Duplicate processing | No idempotency check | Track processed `replayId` values in Redis |
| Outbound Message retry | Ack not sent | Return `<Ack>true</Ack>` XML response |
## Resources
- [Change Data Capture Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.change_data_capture.meta/change_data_capture/)
- [Platform Events Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/)
- [Streaming API](https://developer.salesforce.com/docs/atlas.en-us.api_streaming.meta/api_streaming/)
- [Outbound Messaging](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_om_outboundmessaging.htm)
## Next Steps
For performance optimization, see `salesforce-performance-tuning`.
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.