Skip to contentIntroducing the Integration Factory
Back to Blog
Integration

Salesforce Webhooks for Inbound and Outbound Events

Jan 14, 2026
SCSunny Chauhan
Salesforce Webhooks for Inbound and Outbound Events

To receive a webhook in Salesforce, you write an Apex REST class. To send one, you make an Apex callout from a queued job, or you configure an outbound message, which is a SOAP notification rather than a JSON webhook. Core Salesforce has no settings page where you paste a URL and pick record events. The one place it ships webhooks under that name is Data 360, whose data actions can post signed payloads to your endpoint.

For a SaaS company the harder questions come after the first working endpoint. Your product has to receive changes from hundreds of customer orgs and push updates back into them. Every inbound call spends the customer's daily API allocation, and the code that sends events usually lives in a managed package your customers install. Appnigma works with B2B SaaS teams on that customer-facing integration. Our integration infrastructure lets each customer "authorize their own account from your product, with their connection managed separately," and our marketplace apps work builds "a Salesforce managed package or HubSpot app built around your product and customer workflows" when the event logic has to run inside Salesforce.

Updated October 2026. Checked against the Apex Developer Guide, the SOAP API outbound messaging pages, the Named Credentials guide and Salesforce Help. The earlier version said a provider like Stripe could post to an Apex REST URL on your My Domain, that a Named Credential secures inbound calls, and that platform events push to external systems. None of those match Salesforce's docs. This update corrects them and adds the guest site route with a signature check, the API cost per customer org, and what a managed package can ship.

Which Salesforce feature does the webhook's job

The right Salesforce feature for a webhook depends on the direction of the call, then on who makes it. A provider such as a payment processor can't hold a Salesforce OAuth token. Your own SaaS product can, because each customer already authorized it.

What you needSalesforce featureFormatHow the caller is trustedCost to the customer org
A third party posts events into SalesforceApex REST class exposed on a Salesforce SiteAny body you parseYour code checks an HMAC signatureApex governor limits per call
Your SaaS product posts events into a customer orgApex REST class, called with the customer's OAuth tokenJSON you defineOAuth 2.0One API request per call
Salesforce notifies your product of a record changeApex callout from a Queueable jobJSON you defineNamed credential on the Salesforce side, your auth check on yoursCallout limits, no API request
Salesforce notifies an endpoint from a flow, without ApexOutbound message from a record-triggered flowSOAP, up to 100 notifications per messageOrg ID check plus client certificateNone documented
Your product follows a stream of changesChange Data Capture or platform events over Pub/Sub APIAvro over gRPCOAuth 2.0 per org24-hour event delivery allocation

The API request column comes from Salesforce's statement that "calls to Apex REST classes count against the organization's API governor limits," in Introduction to Apex REST. The outbound message format comes from Setting Up Outbound Messaging, which says "Salesforce sends a SOAP message to this endpoint."

Receiving a webhook from a provider that can't use OAuth

An Apex REST class on the org's My Domain only accepts authenticated calls. Salesforce lists two mechanisms, OAuth 2.0 and a session ID, in Introduction to Apex REST. A provider that signs its payload with a shared secret sends neither, so a URL like https://yourdomain.my.salesforce.com/services/apexrest/orders/webhook rejects it.

The documented route for an unauthenticated caller is a Salesforce Site. You give the site's guest user profile access to the Apex class, and the class becomes reachable at the site URL. Salesforce describes the format as "{Site_URL}/services/apexrest{URL_mapping}" in a Salesforce Developers blog post that is still the only official page we found that states it. Once the class is enabled, "guest and external users can invoke these methods with arbitrary parameters," per Limit Access to Apex Classes. Salesforce does no authentication on that path, so the signature check in your class is the only thing standing between the internet and your data.

Here is the shape of a receiver for an HMAC-signed provider. The method takes no parameters on purpose. "If the Apex method has no parameters, Apex REST copies the HTTP request body into the RestRequest.requestBody property," per Apex REST Methods, and you need the raw bytes to recompute the signature.

@RestResource(urlMapping='/orders/webhook')
global without sharing class OrderWebhook {
  @HttpPost
  global static void receive() {
    RestRequest req = RestContext.request;
    RestResponse res = RestContext.response;
    String sent = req.headers.get('X-Signature');
    String secret = Webhook_Secret__mdt.getInstance('Orders').Value__c;
    Blob mac = Crypto.generateMac('hmacSHA256', req.requestBody, Blob.valueOf(secret));
    if (sent == null || sent != EncodingUtil.convertToHex(mac)) {
      res.statusCode = 401;
      return;
    }
    System.enqueueJob(new OrderWebhookJob(req.requestBody.toString()));
    res.statusCode = 202;
  }
}

Three details in that sample matter more than the parsing. The secret sits in a protected custom metadata type, because Salesforce's secure coding guide says "secrets are never hardcoded in the package code or displayed to the user," in Storing Sensitive Data. The class runs without sharing because a guest user can only update records "in the system context without sharing," per the Unauthenticated Guest User Guidelines. And the real work goes to a Queueable job, so the provider gets its 202 fast and retries less. A status code outside Salesforce's allowed list comes back as a 500, per the RestResponse Class reference. Check the exact header name and signing scheme in your provider's docs, since many sign a timestamp together with the body.

Keep the guest profile narrow. Salesforce's Help guidance is to "allow Apex class access only for REST or SOAP API use," in Best Practices When Configuring the Guest User Profile. Grant the one webhook class and nothing else.

When your own product posts events into a customer org

A SaaS product is not in the provider's position. Each customer already authorized your app with OAuth, so your backend calls the customer's Apex REST class with that token, and no guest site is needed. If the class ships in your managed package, the path includes your namespace. Salesforce's example is https://instance.salesforce.com/services/apexrest/packageNamespace/MyMethod/, from Apex REST Methods. How to register that OAuth app once and use it across many orgs is covered in our guide to OAuth for a SaaS app connecting to many customer orgs.

The cost lands on the customer. Every call draws from the org's rolling 24-hour API allocation. Salesforce's own example in API Request Limits and Allocations is "an Enterprise Edition org with 15 Salesforce licenses." Its daily limit is 100,000 requests plus 1,000 for each license. That makes 115,000.

Here is a worked example from those documented limits, not a measured customer. Your product posts one call per changed record, and a busy customer generates 40,000 changes on a day with a data import. That spends 40,000 of the org's 115,000 requests on one vendor. About 35 percent of the day's allocation is gone before the customer's own middleware and other apps make a call. Batch 200 changes into each request and the same day costs 200 calls. One request can carry "6 MB for synchronous Apex," per Introduction to Apex REST, so the batch size is set by your DML and CPU limits long before the payload size. The usual support ticket after an unbatched rollout is a customer whose other integrations started failing with REQUEST_LIMIT_EXCEEDED, and our guide to each customer org's daily API allocation covers how to plan for it.

Sending record changes out with an Apex callout

Salesforce won't let a trigger call your endpoint directly. "You can't make a callout when there are pending operations in the same transaction," and DML counts as one, per Callout Limits and Limitations. The trigger enqueues a job, the record commits, and the job makes the call.

trigger OrderEvents on Order (after insert, after update) {
  System.enqueueJob(new SendOrderEvent(new List<Id>(Trigger.newMap.keySet())));
}
public class SendOrderEvent implements Queueable, Database.AllowsCallouts {
  public class EventRejectedException extends Exception {
  }
  private final List<Id> orderIds;
  public SendOrderEvent(List<Id> orderIds) {
    this.orderIds = orderIds;
  }
  public void execute(QueueableContext ctx) {
    HttpRequest req = new HttpRequest();
    req.setEndpoint('callout:Vendor_Events/v1/events');
    req.setMethod('POST');
    req.setHeader('Content-Type', 'application/json');
    req.setTimeout(20000);
    req.setBody(JSON.serialize(new Map<String, Object>{
      'orgId' => UserInfo.getOrganizationId(),
      'orderIds' => orderIds
    }));
    HttpResponse res = new Http().send(req);
    if (res.getStatusCode() >= 300) {
      throw new EventRejectedException('Endpoint returned ' + res.getStatusCode());
    }
  }
}

Queueable jobs may call out "if they implement the Database.AllowsCallouts marker," per Queueable Apex. Each transaction allows 100 callouts and "the maximum cumulative timeout for callouts by a single Apex transaction is 120 seconds." The default timeout per call is 10 seconds, both from Callout Limits and Limitations. Salesforce does not retry a failed callout for you. A transaction finalizer can, since "a Queueable job that failed due to an unhandled exception can be successively re-enqueued five times by a transaction finalizer," per Transaction Finalizers. That is why the sample throws on a non-2xx response instead of logging and moving on.

The callout:Vendor_Events endpoint is a named credential. It handles the outbound side only. A named credential "specifies the URL of a callout endpoint and its required authentication parameters," per Named Credentials as Callout Endpoints, and it plays no part in authenticating calls coming in.

For a vendor, the named credential is also what ships in the package. Since February 2026, "named credentials added to a managed package default to developer control," so customers can't change your URL, per Package Named Credentials. The same page says "external credential certificates and access tokens aren't packageable." Each customer fills in the token after install, which belongs in your install steps. Our guide to named credentials in a managed package walks through that setup.

Are Salesforce outbound messages webhooks?

They do the same job with different mechanics. An outbound message is a SOAP post that "can include up to 100 notifications," per Understanding Notifications. Your listener has to parse SOAP and send back an acknowledgment, which most JSON webhook receivers can't do without changes.

The delivery rules shape the receiver. Salesforce retries with growing intervals and drops a message from the queue "after 24 hours." Retries ignore order, since "messages are retried independent of their order in the queue." A message can also arrive more than once, so "check the notification IDs" before processing. All three come from Understanding Notifications. Support can extend the window to seven days, "but automatic retries are limited to 24 hours," per the Fire and Forget pattern.

For a SaaS listener that receives messages from many orgs, Salesforce's security page says to "validate that messages contain your organization Id," and warns that an IP allowlist "doesn't guarantee that another customer isn't pointing to your endpoint," in Considerations for Security. Map each org ID you expect to a customer, and reject the rest.

Outbound messages now start from flows. Since Winter '22, "outbound message actions are available for record-triggered flows that run after the record is saved," per the release note. Salesforce "no longer supports Workflow Rules and Process Builder as of December 31, 2025," though existing rules keep running, per this Help article. Build new ones on flows.

Flow also has an HTTP Callout action that posts JSON through a named credential. A response code "of 300 or greater is returned as an error" that you handle on a fault path, and request headers on the action itself "aren't supported," per HTTP Callout Considerations and Limits. It suits a customer's admin wiring one endpoint. A vendor sending events from every customer org usually wants the Apex version, which it can test, package and retry.

When a stream fits better than a webhook

Platform events and Change Data Capture don't post to a URL. An external system subscribes through Pub/Sub API, which "is pull-based, which means that it requests new events," per the Subscribe RPC reference. Your product holds an open subscription per customer org, and Salesforce stores the events "for 72 hours" so you can resume after an outage, per Event Message Durability.

That trade works well when your product needs every change on a few objects and can run a subscriber service. It costs the customer's event delivery allocation rather than API requests. Publishing from Apex or flows doesn't consume the daily API request allocation either, per Default Platform Event Allocations. Our comparison of Platform Events vs Change Data Capture covers the shared delivery allocation and the managed package rules in detail.

Two Salesforce features do push without a subscriber. Event Relay "relays platform events and change data capture events from Salesforce to Amazon EventBridge," per EventRelayConfig, which fits a customer that already runs on AWS. Data 360 data actions post to webhook targets and sign each payload with HMAC-SHA256, per Webhook Data Action Targets in Data 360. That second one only helps if the customer licenses Data 360.

Mistakes that show up after customers install

Each of these comes from a documented limit or behavior, and each one turns into a support ticket from a customer rather than an error in your test org.

  • One API request per record. An unbatched inbound webhook spends the customer's allocation, and the other apps in that org fail first.
  • Callouts written in the trigger. The code passes in a test that mocks the callout, then fails in the customer's org with uncommitted work pending.
  • No dedupe on the receiving side. Outbound messages can arrive twice, and a finalizer retry resends the whole batch, so key your writes on the record ID plus its last modified time.
  • Secrets in code or a public custom setting. The security review expects protected custom metadata or a protected custom setting, per Storing Sensitive Data.
  • Packaged remote site settings with no explanation. At install, "you must approve access to websites outside of Salesforce," per Install a Managed Package. Admins who don't recognise the domain stop the install.
  • A guest site the customer has to build. If your package relies on a third party posting into each customer org, every admin has to create a site and grant the guest profile access. Routing those events through your own backend, which already holds OAuth tokens, removes that step.

Frequently Asked Questions

Does Salesforce support webhooks?

Yes, in both directions, under other names. Apex REST receives them, Apex callouts and outbound messages send them, and Data 360 data actions are the one feature Salesforce calls webhooks.

What is the webhook URL for an Apex REST class?

For an authenticated caller it is https://yourdomain.my.salesforce.com/services/apexrest/ plus your URL mapping, with your namespace first if the class is in a managed package. A caller with no OAuth token uses the Salesforce Site URL followed by /services/apexrest and the mapping.

Do Salesforce webhooks count against API limits?

Inbound calls to an Apex REST class do, one request each. Outbound Apex callouts don't use API requests but are capped at 100 per transaction, and platform event delivery to external subscribers uses the separate event delivery allocation.

Can a Salesforce outbound message send JSON?

No. Outbound messages are SOAP. To send JSON, use an Apex callout from a Queueable job or Flow's HTTP Callout action.

Can Appnigma build our Salesforce webhook integration?

Yes. Appnigma's done for you delivery builds the integration "with the right APIs, authentication, and platform-specific code," and marketplace apps covers the managed package when the event logic runs inside each customer's org.

Sources

  1. Salesforce Developers, Apex Developer Guide, Introduction to Apex REST
  2. Salesforce Developers, Apex Developer Guide, Apex REST Methods
  3. Salesforce Developers, Apex Reference Guide, RestResponse Class
  4. Salesforce Developers Blog, Public RESTful Web Services on Salesforce Sites
  5. Salesforce Developers, Experience Cloud Developer Guide, Limit Access to Apex Classes
  6. Salesforce Developers, Experience Cloud Developer Guide, Unauthenticated Guest User Guidelines
  7. Salesforce Help, Best Practices When Configuring the Guest User Profile
  8. Salesforce Developers, Salesforce Limits, API Request Limits and Allocations
  9. Salesforce Developers, Apex Developer Guide, Callout Limits and Limitations
  10. Salesforce Developers, Apex Developer Guide, Queueable Apex
  11. Salesforce Developers, Apex Developer Guide, Transaction Finalizers
  12. Salesforce Developers, Apex Developer Guide, Named Credentials as Callout Endpoints
  13. Salesforce Developers, Named Credentials Developer Guide, Package Named Credentials
  14. Salesforce Developers, SOAP API Developer Guide, Setting Up Outbound Messaging
  15. Salesforce Developers, SOAP API Developer Guide, Understanding Notifications
  16. Salesforce Developers, SOAP API Developer Guide, Considerations for Security
  17. Salesforce Developers, Integration Patterns and Practices, Remote Process Invocation, Fire and Forget
  18. Salesforce Help, Send an Outbound Message from Your Record-Triggered Flow (Winter '22)
  19. Salesforce Help, Workflow Rules and Process Builder End of Support
  20. Salesforce Help, HTTP Callout Considerations and Limits
  21. Salesforce Developers, Pub/Sub API Developer Guide, Subscribe RPC Method
  22. Salesforce Developers, Pub/Sub API Developer Guide, Event Message Durability
  23. Salesforce Developers, Platform Events Developer Guide, Default Platform Event Allocations
  24. Salesforce Developers, Field Reference, EventRelayConfig
  25. Salesforce Developers, Data 360 Integration Guide, Webhook Data Action Targets in Data 360
  26. Salesforce Developers, Secure Coding Guide, Storing Sensitive Data
  27. Salesforce Developers, First-Generation Managed Packaging Developer Guide, Install a Managed Package

All Blogs

Bring the customer requirements and your current setup. Get a clear scope and delivery timeline to take back to them.