Salesforce Integration Simplified: Webhooks, Platform Events & Callouts
Salesforce webhooks are at the heart of modern, two-way integrations between Salesforce and external systems. Instead of simple one-way data syncs, your org can now have real-time conversations: external services notify Salesforce instantly, and Salesforce can call back out to complete the loop.
-
Inbound: External apps send events into Salesforce using webhooks, Apex REST, or Platform Events.
-
Outbound: Salesforce pushes or requests data from other systems using HTTP callouts or Outbound Messaging.
This guide walks you through both directions step by step, with real code examples, configuration tips, and best practices to help you design secure, scalable, and reliable Salesforce webhooks–based integrations.
What Are Salesforce Webhooks?
At a high level, Salesforce webhooks let an external system notify Salesforce in real time when something important happens. Instead of polling, you simply expose an endpoint or event channel and let the external system push data in.
There are two common ways to implement this pattern:
-
Apex REST “Webhook Receiver”
-
You expose a REST endpoint with
@RestResource. -
External systems (payment providers, SaaS apps, IoT devices, etc.) make an HTTP
POSTto that endpoint.
-
-
Inbound Platform Events
-
You define a custom Platform Event like
Order_Updated__e. -
External apps publish events via the REST API or Pub/Sub API.
-
Salesforce reacts asynchronously using triggers, Flows, or subscribers.
-
You’ll often see Change Data Capture (CDC) used alongside these approaches—for example, inbound Platform Events update Salesforce, and CDC then fans those changes out to other downstream systems.
External reference: Learn more about Salesforce Platform Events and Change Data Capture.
Inbound Salesforce Webhooks with Apex REST
1️⃣ Inbound Webhook → Apex REST (Handle External Callbacks)
Goal: Receive a webhook from a payment gateway and update an Opportunity in Salesforce.
@RestResource(urlMapping='/webhooks/payments')
global with sharing class PaymentsWebhook {
global class Event {
public String eventType;
public String orderId;
public String status;
public Decimal amount;
}
@HttpPost
global static void handle() {
// 1) Verify signature (example HMAC header)
String sig = RestContext.request.headers.get('X-Signature');
String body = RestContext.request.requestBody.toString();
// validateSignature(sig, body); // implement HMAC check — critical
// 2) Parse and process
Event e = (Event) JSON.deserialize(body, Event.class);
Opportunity o = [
SELECT Id, StageName
FROM Opportunity
WHERE Order_Id__c = :e.orderId
LIMIT 1
];
if (e.status == 'succeeded') {
o.StageName = 'Closed Won';
} else if (e.status == 'failed') {
o.StageName = 'Closed Lost';
}
update o;
// 3) Return 200 OK
RestContext.response.statusCode = 200;
}
}
Setup checklist for Apex-based Salesforce webhooks:
-
Deploy the Apex class and map it to a URL with
@RestResource. -
Expose it via a Salesforce Site, Experience Cloud site, or secure middleware proxy.
-
Protect it with HMAC signatures, IP allowlists, OAuth, or mTLS.
-
Never expose a public webhook endpoint without authentication or verification.
? Internal link idea: For a deeper dive, see your own guide on Salesforce Named Credentials or Salesforce REST API basics.
Inbound Salesforce Webhooks with Platform Events
2️⃣ Inbound via Platform Events (External Publishes → Salesforce Reacts)
Goal: An external order system publishes an Order_Updated__e Platform Event. Salesforce updates records based on the event.
Step 1: Create a Platform Event
Example fields:
-
OrderId__c(Text) -
Status__c(Text) -
Total__c(Number)
Step 2: External Publish (via REST API)
curl -X POST https://<instance>.salesforce.com/services/data/v61.0/sobjects/Order_Updated__e \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{ "OrderId__c":"ORD-1001", "Status__c":"Packed", "Total__c":199.99 }'
Step 3: Salesforce Subscriber Trigger
trigger OrderUpdatedTrigger on Order_Updated__e (after insert) {
List<Order> toUpdate = new List<Order>();
for (Order_Updated__e evt : Trigger.New) {
for (Order o : [
SELECT Id, Status__c
FROM Order
WHERE ExternalId__c = :evt.OrderId__c
LIMIT 1
]) {
o.Status__c = evt.Status__c;
toUpdate.add(o);
}
}
if (!toUpdate.isEmpty()) {
update toUpdate;
}
}
? Pro Tip: External publishers can also use the Pub/Sub API for durable delivery and replay support—ideal when you rely heavily on Salesforce webhooks and event-driven patterns.
Salesforce Webhooks and Change Data Capture (CDC)
3️⃣ CDC → External Subscriber (Node.js + CometD)
Goal: Stream record changes out of Salesforce to external services (search indexes, data lakes, enrichment pipelines, etc.) in real time.
// Node.js CDC subscriber (CometD/Bayeux)
import cometd from "cometd";
const c = new cometd.CometD();
c.configure({
url: "https://<instance>.salesforce.com/cometd/61.0",
requestHeaders: { Authorization: "OAuth <access_token>" }
});
c.handshake((_h) => {
c.subscribe("/data/ChangeEvents", (msg) => {
console.log("CDC:", JSON.stringify(msg.data, null, 2));
// use msg.data.payload.* and replayId for resume
});
});
Best practices for CDC and Salesforce webhooks:
-
Persist
replayIdso you can resume streaming after outages. -
Use a Dead Letter Queue (DLQ) to capture failures for safe replay.
-
Consider routing CDC events into queues or streaming platforms like Kafka.
? External reference: See the official Salesforce Streaming API and CometD docs.
Outbound Integrations: HTTP Callouts and Outbound Messaging
4️⃣ Outbound HTTP Callout → External REST API (Apex + Named Credential)
Goal: Automatically create a ticket in an external helpdesk system whenever an issue is raised in Salesforce.
public with sharing class HelpdeskClient {
public class Ticket {
public String title;
public String description;
public String priority;
public Ticket(String t, String d, String p) {
title = t; description = d; priority = p;
}
}
public static String createTicket(String title, String desc) {
HttpRequest req = new HttpRequest();
req.setMethod('POST');
req.setEndpoint('callout:HelpdeskAPI/v1/tickets'); // Named Credential base
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Ticket(title, desc, 'High')));
HTTPResponse res = new HTTP().send(req);
if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
return res.getBody();
}
throw new CalloutException(
'Helpdesk error ' + res.getStatus() + ': ' + res.getBody()
);
}
}
Setup tips:
-
Use Named Credentials and External Credentials for secure, centralized authentication.
-
Trigger callouts from Apex, Flows (HTTP Callout actions), or Invocable methods.
-
Combine outbound callouts with Salesforce webhooks inbound flows for full two-way syncs.
? Internal link idea: Cross-link to your own post on Salesforce integration patterns.
5️⃣ Outbound (No-Code) → Outbound Message to a Webhook Endpoint
Goal: Notify an external URL whenever an Account record is updated.
Steps:
-
Create a Workflow Rule or Record-Triggered Flow.
-
Add an Outbound Message action.
-
Select fields to send and provide your HTTPS endpoint (Pipedream, MuleSoft, AWS API Gateway, etc.).
-
Salesforce retries automatically on failure. Your endpoint must respond with
200 OK.
Sample XML Payload (Simplified):
<notifications xmlns="http://soap.sforce.com/2005/09/outbound">
<Notification>
<sObject xsi:type="sf:Account"
xmlns:sf="urn:sobject.enterprise.soap.sforce.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<sf:Id>001...</sf:Id>
<sf:Name>Acme</sf:Name>
<sf:Website>https://acme.example</sf:Website>
</sObject>
</Notification>
</notifications>
Outbound Messaging gives you a low-code way to complement your Salesforce webhooks and Apex integrations.
Salesforce Webhooks Best Practices & Guardrails
Use this checklist to keep your salesforce webhooks and integrations secure, reliable, and scalable:
-
? Authentication & Verification
Secure inbound webhooks with HMAC signatures, IP allowlists, JWT, or mutual TLS. -
? Idempotency
Use External IDs or unique request keys to prevent duplicate updates when messages are retried. -
⚙️ Backpressure & Retries
Offload heavy work to Queueable Apex, Platform Events, or asynchronous Flows to avoid governor limit issues. -
?️ Observability
Pass a correlation ID through the entire integration chain (headers, logs, payloads) for easy debugging. -
? Error Handling & DLQs
Send failed payloads to a Dead Letter Queue (custom object, topic, or external queue) for replay. -
? Security & Permissions
Respect CRUD/FLS before DML; for UI-focused integration, consider UI API or GraphQLuiapi. -
? Governor Limits & Bulkification
Bulkify queries and DML; move heavy processing to async jobs (Queueables, Batch Apex, Scheduled Apex).
? External reference: Check the official Salesforce Integration Best Practices for evolving patterns and guidance.
? Final Thoughts
Treat inbound and outbound integrations as equal halves of one reliable data pipeline.
Accept events securely using webhooks or Platform Events, process them asynchronously, and send data out with authenticated callouts.
By adding idempotency, correlation IDs, and DLQs, your integrations become resilient, traceable, and self-healing — quietly doing their job behind the scenes.

