---
name: stripe-salesforce
description: Build a production-grade Stripe to Salesforce integration covering architecture, the step-by-step build with Apex code, field mapping, API and governor limits, security, monitoring, and the pitfalls to design out. Use when connecting, building, or debugging a Stripe and Salesforce integration.
---

# Stripe to Salesforce integration

> Cloudsheer's delivery playbook for Stripe on Salesforce, distilled from 14 client projects and 214 delivery tasks. Apply it as the reference approach when building this integration.

## Overview

The workhorse payment gateway across our Salesforce builds, from checkout and subscriptions to donations and reconciliation. We have shipped it across 16 client projects and 214 build tasks.

The value is what happens after the charge: matching payments to records, handling refunds, and keeping finance reconciled without manual work.

We build a hardened webhook pipeline: a public Apex REST endpoint on a Salesforce Site, signature verification on every event, and flows that turn raw Stripe events into clean, reconciled records.

Every Stripe build is delivered by a senior Salesforce architect on a fixed price, tested end to end in a sandbox, deployed to your org, and backed by 30 days of hypercare. You own the result: documented, source-controlled, and free of black-box middleware lock-in.

## Integration facts

**Connects via:**
- Managed package (AppExchange): Stripe app for Salesforce Platform
- Pre-built Flow invocable actions plus Apex (AgnosticInvocable for uncovered endpoints)
- Inbound Stripe webhooks logged to a Stripe Events object, then processed in Flow

**Package:** Stripe app for Salesforce Platform

**Authentication:** Stripe secret / restricted API key (Bearer) outbound; inbound webhooks verified with an HMAC-SHA256 endpoint signing secret via the Stripe-Signature header

**API type:** REST+Webhooks

**API base:** `https://api.stripe.com/v1`

**Key endpoints:**
- `/v1/checkout/sessions`
- `/v1/payment_links`
- `/v1/charges`
- `/v1/payment_intents`
- `/v1/customers`

**Webhook and platform events:**
- `checkout.session.completed`
- `checkout.session.expired`
- `charge.succeeded`
- `payment_intent.succeeded`
- `invoice.paid`

**Official docs:** https://docs.stripe.com/use-stripe-apps/stripe-app-for-salesforce/overview

## Prerequisites

- A Salesforce edition with API access (Enterprise, Unlimited, or Developer)
- A dedicated sandbox to build and test in
- Stripe test-mode credentials to validate before going live
- A Salesforce Site to host the public webhook endpoint
- A Stripe account on a plan with API access
- System Administrator access on both systems
- A dedicated integration user with a minimum-access permission set
- Agreement on the objects, fields, and sync direction for the Stripe data

## Architecture

Data flows left to right through four lanes:

1. **Sources:** Stripe Checkout, Payment Links, Webhook events, Chargent Anywhere
2. **Integration layer:** Webhook REST controller, Payment batch jobs, Record-triggered flows, Refund helper
3. **Salesforce:** Account, Invoice / Payment, Opportunity, Case
4. **Outcomes:** Auto-reconciled payments, Donation and gift records, Zero manual entry

## How it works at runtime

1. **Customer pays** `[In Stripe]`: A customer or donor pays through a Stripe payment link on a Case or Account, or a link embedded in an invoice or email.
   - Note: `Card data stays in Stripe; Salesforce only ever sees tokens and ids, keeping PCI scope low.`
2. **Stripe fires a webhook** `[In transit]`: Events such as checkout.session.completed and charge.succeeded post to a secured Apex REST endpoint.
   - Note: `Delivered to a public /services/apexrest/ URL on a Salesforce Site.`
3. **Verified and routed** `[In Salesforce]`: The controller verifies the signing secret, guards against duplicates and double-charges, and returns 200 fast.
   - Note: `Signature validated with the webhook secret; processing continues async in a Queueable.`
4. **Records created or updated** `[In Salesforce]`: Flows create the payment or gift record and an Account for unknown donors, with Stripe ids stored.
   - Note: `Idempotent upsert on the event id via SalesforceStripeWebhooksController.`
5. **Reconcile and report** `[In Salesforce]`: The payment is reconciled to the Account or invoice, methods are backfilled, and refunds flow back.
   - Note: `FetchAndUpdateStripePaymentBatch reconciles; RefundInvoiceFromStripeHelper handles refunds.`

## Step-by-step build

### Step 1: Plan the integration and prerequisites

We agree the events that matter and design a secure public endpoint before touching code.

- A Salesforce edition with API access, a dedicated sandbox, and a Salesforce Site for the public URL
- A Stripe account in test mode first, with admin access on both systems
- The exact events, target objects, and reconciliation rules agreed up front
- A hardened Site guest user with the absolute minimum permissions

### Step 2: Build the Apex REST endpoint

We give Stripe a typed, testable place to POST events.

- An @RestResource class with an @HttpPost handler mapped to a stable URL
- The raw request body is read once and kept for signature verification and audit

Reference implementation (`snippets/InboundWebhookResource.cls`):

```apex
@RestResource(urlMapping='/webhook/*')
global with sharing class InboundWebhookResource {
  @HttpPost
  global static void handle() {
    RestRequest req = RestContext.request;
    String raw = req.requestBody.toString();

    if (!WebhookSignature.isValid(raw, req.headers.get('X-Signature'))) {
      RestContext.response.statusCode = 401;      // reject unverified events
      return;
    }
    Inbound_Event__c e = new Inbound_Event__c(
      Event_Id__c = EventParser.idOf(raw), Payload__c = raw);
    upsert e Event_Id__c;                         // idempotent capture
    System.enqueueJob(new EventProcessor(e.Id));  // process asynchronously
    RestContext.response.statusCode = 200;         // respond fast
  }
}
```

### Step 3: Expose it securely on a Salesforce Site

We make the endpoint reachable without opening the whole org.

- Create a Salesforce Site and enable only the one Apex class for the guest user
- The public URL follows /services/apexrest/...; every other guest permission stays off

> **Watch out: lock down the guest user** A Salesforce Site runs as a guest user. Grant it access to only the one Apex class, or you expose far more of the org than a webhook ever should.

### Step 4: Register the webhook in Stripe

We subscribe to exactly the events we need, nothing more.

- In the Stripe dashboard, add the endpoint URL and select the relevant events (checkout, charge, invoice, refund, and so on)
- Copy the webhook signing secret into a protected custom setting or custom metadata

### Step 5: Verify signatures and prevent replay

We make sure only genuine, once-only events ever change data.

- Compute an HMAC-SHA256 of the raw body with the signing secret and constant-time compare it
- Reject on mismatch, and check the event timestamp to block replay attacks

Reference implementation (`snippets/WebhookSignature.cls`):

```apex
public class WebhookSignature {
  public static Boolean isValid(String rawBody, String header) {
    Blob secret = Blob.valueOf(WebhookConfig.signingSecret());
    Blob mac = Crypto.generateMac('HmacSHA256', Blob.valueOf(rawBody), secret);
    String expected = EncodingUtil.convertToHex(mac);
    // constant-time compare guards against timing attacks
    return ConstantTime.equals(expected, header);
  }
}
```

> **Watch out: verify every event** A public endpoint is a target. Validate the signing secret and make the handler idempotent, or a retried or spoofed event can double-post to your records.

### Step 6: Respond fast, process asynchronously

We never let processing block the webhook response.

- Return HTTP 200 within the vendor timeout, which is usually only a few seconds
- Hand the heavy work to a Queueable so slow processing never triggers a retry storm

Reference implementation (`snippets/EventProcessor.cls`):

```apex
public class EventProcessor implements Queueable {
  private Id eventId;
  public EventProcessor(Id eventId) { this.eventId = eventId; }

  public void execute(QueueableContext ctx) {
    Inbound_Event__c e = [SELECT Payload__c FROM Inbound_Event__c WHERE Id = :eventId];
    Map<String,Object> body = (Map<String,Object>) JSON.deserializeUntyped(e.Payload__c);
    // heavy work runs here, off the webhook thread: map onto Accounts, Cases, Payments
    EventRouter.route(body);
    update new Inbound_Event__c(Id = eventId, Processed__c = true, Processed_At__c = System.now());
  }
}
```

### Step 7: Capture raw events idempotently

We keep a durable, replayable record of everything received.

- Upsert an Inbound_Event__c on the event id so duplicate deliveries are ignored
- Store the raw JSON for audit and for replay if a downstream mapping ever changes

### Step 8: Map events onto your Payment

We turn raw Stripe events into clean Salesforce data.

- Flows or triggers translate events onto Accounts, Payment, Payments, and Cases
- Store the Stripe ids on the records and handle out-of-order events gracefully

### Step 9: Handle money and edge cases

We cover the cases that otherwise become disputes.

- Reconcile payments, and handle refunds, chargebacks, and partial captures
- Backfill missing data and alert on any mismatch before it reaches finance

### Step 10: Test, deploy, and monitor

We prove it end to end and keep watch in production.

- Apex tests build a RestContext request and assert the resulting records; replay real test-mode events
- Deploy via change sets, restrict the events object to admins, and monitor with error logging plus 30 days of support

## Data model

| Object | Purpose | Key fields |
| --- | --- | --- |
| `Payment` | The primary Salesforce record Stripe data maps onto. | `External_Id__c, Name, Status` |
| `Inbound_Event__c (custom)` | Stores each raw event idempotently for audit and replay. | `Event_Id__c, Payload__c, Processed__c` |
| `Account` | Matched or created for the customer or company behind the record. | `Name, External_Id__c` |
| `Error_Log__c (custom)` | Captures every request, response, and failure so anything can be replayed. | `Payload__c, Status__c, Related_Id__c` |

Salesforce objects typically in play: `Stripe Events (managed object)`, `Account`, `Contact`, `Opportunity`, `Case`

## Field mapping (example)

| Stripe | Salesforce | Notes |
| --- | --- | --- |
| Stripe charge id | `Payment.External_Id__c` | Unique external id, upsert key |
| Stripe amount | `Payment.Amount` |  |
| Stripe currency | `Payment.CurrencyIsoCode` |  |
| Stripe customer | `Account` | Matched or created |
| Stripe status | `Payment.Status` | Picklist value mapping |
| Created / updated at | `LastModifiedDate` | Enables delta sync and audit |
| Owner or rep | `Payment.OwnerId` | Assignment rules or a default owner |

Tailor the full mapping to the org. Always upsert on an external-id field so retries are idempotent.

## API and rate limits

### Stripe-specific

- Stripe API ~100 read + 100 write requests/sec live (25/sec test)
- Webhook endpoints must return 2xx quickly or Stripe retries with backoff for up to ~3 days
- Salesforce Apex callout limits: 100 callouts/transaction, 120s

### Salesforce platform

- A webhook handler must return within Stripe's timeout, usually a few seconds. We acknowledge with HTTP 200 immediately and process the event asynchronously.
- Salesforce Sites have their own request limits. Heavy processing is offloaded to a Queueable so the public endpoint stays fast.
- Stripe retries failed deliveries automatically. Idempotency on the event id means a retried event is never processed twice.

## Security checklist

- Secrets stored in Named Credentials and permission sets, never in code or metadata
- A least-privilege integration user, with field-level security and sharing scoped tight
- All traffic over TLS, with signature verification on inbound events
- Card data never touches Salesforce, keeping your PCI scope minimal
- Shield Platform Encryption available for sensitive fields
- A full audit trail: every request and response logged for traceability
- Every automation runs as a dedicated integration user, so actions are attributable and revocable
- Sandbox-first delivery and change-set deployment keep production changes reviewed and controlled

## Monitoring and reliability

- Every request and response is logged to a custom Error Log object, tagged with the related record id.
- Failed calls retry with exponential backoff; anything still failing lands in a dead-letter queue for review.
- Idempotency keys guarantee a retried or duplicate event never double-posts a record.
- A dashboard surfaces failures, latency, and volume so problems are caught before users notice.
- Optional email or Slack alerts fire on repeated failures or a stalled sync.

## Testing and deployment

- Apex unit tests with HttpCalloutMock cover the success path, failure handling, and a 200-record bulk case, at 75 percent or higher coverage.
- The full flow is validated in a sandbox against real sample data and the edge cases that matter.
- A parallel run reconciles the integration against your live system before cutover.
- Everything deploys through change sets or an SFDX and CI pipeline, under version control.
- Permission sets, sharing, and Named Credentials are configured in production, then we run 30 days of monitored hypercare.

## Pitfalls to design out

- **Missed or duplicated events:** Verify signatures, upsert on the event id, and return 200 within the timeout.
- **Webhook times out on heavy processing:** Acknowledge fast and process the event in a Queueable.
- **Guest user exposes too much:** Grant the Site guest user access to only the single Apex class.
- **No visibility when it breaks:** We log every call and surface failures on a dashboard with alerts, so an issue never goes unnoticed.
- **Reporting drifts from reality:** External-id keys and a delta timestamp keep Salesforce and the source reconciled, so reports stay trustworthy.

### Stripe-specific gotchas

- Each webhook endpoint has its own signing secret; a wrong or rotated secret silently fails verification
- Events must be explicitly enabled and field-mapped to Salesforce objects or nothing syncs
- Endpoints outside the API Extension pack require the AgnosticInvocable action

## FAQ

**How do you authenticate Stripe with Salesforce?**

We connect Stripe using named credentials and API keys and store every secret in Salesforce Named Credentials with a permission set, so nothing is hard-coded or shipped in metadata.

**Does the Stripe integration handle bulk volume?**

Yes. All Apex is bulkified, volume moves to Queueable or Batch Apex, and we respect the Salesforce governor limits (SOQL, DML, and callout caps per transaction).

**How do you prevent duplicate records?**

We upsert on a unique external-id field, so a retried or duplicate payload is idempotent and never creates a second Payment.

**How is the integration tested and deployed?**

Apex tests with HttpCalloutMock cover the success, failure, and a 200-record bulk case (75 percent plus coverage). We deploy via change sets or an SFDX and CI pipeline.

**What happens if Stripe or Salesforce is briefly down?**

Failed calls retry with backoff and land in an Error Log object with alerting, so nothing is lost and any event can be replayed.

**How do you secure the webhook endpoint?**

The Apex REST endpoint runs on a Salesforce Site with a locked-down guest user, verifies the HMAC signature on every event, and checks the timestamp to block replays.

---

Maintained by [Cloudsheer](https://www.cloudsheer.com). Full illustrated guide: [Stripe technical guide](https://www.cloudsheer.com/integrations/stripe/technical-guide). Want it built for you at a fixed price? [Book a free 30-minute call](https://cal.com/cloudsheer-consulting/30min?overlayCalendar=true).
