agentsclimarketplace

Salesforce webhooks events

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/salesforce-pack/skills/salesforce-webhooks-events

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill salesforce-webhooks-events

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Implement Salesforce Platform Events, Change Data Capture (CDC), and Outbound Messages.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.5 KB, as published. Nobody here has run it

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

MechanismDirectionUse CaseRetention
Platform EventsBi-directionalCustom event bus72 hours
Change Data Capture (CDC)Salesforce → ExternalRecord change notifications3 days
Outbound MessagesSalesforce → ExternalWorkflow-triggered HTTP POSTUntil confirmed
Streaming API (PushTopics)Salesforce → ExternalSOQL-based subscriptionsNo replay

Instructions

Step 1: Subscribe to Change Data Capture (CDC)

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

// 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)

// 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

// 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

IssueCauseSolution
403: CDC not enabledObject not selected for CDCSetup > Change Data Capture > select objects
EVENT_OR_PUSHTTOPIC_NOT_FOUNDPlatform Event doesn't existCreate in Setup > Platform Events
Missed eventsClient disconnectedUse replayId to resume from last position
Duplicate processingNo idempotency checkTrack processed replayId values in Redis
Outbound Message retryAck not sentReturn <Ack>true</Ack> XML response

Resources

Next Steps

For performance optimization, see salesforce-performance-tuning.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.