# Salesforce Webhooks (Outbound Messages)

Salesforce has no classic HMAC-signed webhook. The closest native push is the
Outbound Message, configured on a Flow or Workflow Rule, which
POSTs a SOAP/XML envelope to your HTTPS endpoint when a record changes.

There is no signature header. You authenticate the message by:

1. Validating `<OrganizationId>` in the SOAP body against your known 18-char Salesforce org id.
2. Enforcing HTTPS and restricting inbound traffic to Salesforce IP ranges.
3. Optionally mutual TLS (Salesforce can present a client certificate).

Your endpoint must return a SOAP `<Ack>true</Ack>` envelope with HTTP `200`,
or Salesforce retries the message for up to 24 hours.

## When to Use This Skill

* How do I receive Salesforce webhooks / Outbound Messages?
* How do I parse the Salesforce Outbound Message SOAP/XML body?
* How do I validate the OrganizationId on a Salesforce Outbound Message?
* What Ack response does a Salesforce Outbound Message listener return?
* How do I handle Account, Opportunity, Contact, Lead, or Case record changes?
* Should I use Outbound Messages, Platform Events, or Change Data Capture?

## Verification (core)

Outbound Messages are unsigned. "Verification" = parse the SOAP body, match
`<OrganizationId>` (timing-safe) against your org id, then return the Ack. Use
the raw request body — Salesforce sends `Content-Type: text/xml`, so parse
XML, not JSON.

```javascript
const { XMLParser } = require('fast-xml-parser');
const crypto = require('crypto');

// removeNSPrefix strips soapenv:/sf: prefixes so we can read plain element names.
const parser = new XMLParser({ ignoreAttributes: false, removeNSPrefix: true });

// rawXml = raw HTTP body string; expectedOrgId = your 18-char Salesforce org id.
function verifyOutboundMessage(rawXml, expectedOrgId) {
  const msg = parser.parse(rawXml)?.Envelope?.Body?.notifications;
  const orgId = String(msg?.OrganizationId ?? '');
  const a = Buffer.from(orgId), b = Buffer.from(expectedOrgId);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    throw new Error('OrganizationId mismatch — reject with 401');
  }
  return msg; // dispatch [].concat(msg.Notification) then return the Ack envelope below
}

```

The Ack envelope every successful listener must return (`Content-Type: text/xml`, HTTP 200):

```xml
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <notificationsResponse xmlns="http://soap.sforce.com/2005/09/outbound">
      <Ack>true</Ack>
    </notificationsResponse>
  </soapenv:Body>
</soapenv:Envelope>

```

> For complete handlers with SOAP parsing, event dispatch, the Ack response, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/salesforce-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/salesforce-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/salesforce-webhooks/examples/fastapi/)

## Common Event Types

Outbound Messages don't carry an event-name string. The "event" is the sObject
type the Flow/Workflow Rule is built on, plus the create/update action. Each
`<Notification>` contains one `<sObject>` whose `xsi:type` attribute is the type:

| sObject (`xsi:type`) | Triggered When | Common Use Cases |
| --- | --- | --- |
| `Account` | Account created/updated | Sync CRM accounts, provision customers |
| `Contact` | Contact created/updated | Sync contacts, update mailing lists |
| `Lead` | Lead created/updated | Route leads, trigger enrichment |
| `Opportunity` | Opportunity created/updated/stage change | Update forecasts, notify sales |
| `Case` | Case created/updated | Sync support tickets, alert on escalation |

A single message can include up to 100 `<Notification>` elements. Messages may
arrive out of order and more than once — handle idempotently on the
`<Notification>` `<Id>` or the sObject `<sf:Id>`.

> Full reference: [Outbound Messaging](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_om_outboundmessaging.htm)

## Streaming Alternative

For high-volume, real-time, or richer event streams, use Platform Events or
Change Data Capture (CDC) consumed over the Pub/Sub API (or the legacy
CometD Streaming API) rather than Outbound Messages. See
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/salesforce-webhooks/references/overview.md).

## Environment Variables

```bash
SALESFORCE_ORG_ID=00Dxx0000000000EAA   # Your 18-char org id (Setup → Company Information)

```

## Local Development

```bash
# Start tunnel (no account needed) — point your Outbound Message endpoint at the Hookdeck URL
npx hookdeck-cli listen 3000 salesforce --path /webhooks/salesforce

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/salesforce-webhooks/references/overview.md) - Outbound Messages, Platform Events, CDC, common sObjects
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/salesforce-webhooks/references/setup.md) - Configure the Outbound Message in Salesforce Setup
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/salesforce-webhooks/references/verification.md) - OrganizationId validation, IP allowlist, mTLS, gotchas