Connect Stripe with Salesforce

Implementation Guide

Overview: Connecting Stripe and Salesforce

For SaaS businesses and subscription-first companies, Stripe and Salesforce occupy two fundamentally different but equally important roles in the revenue stack. Stripe is the payment execution layer—it processes charges, manages subscriptions, handles dunning, and holds the authoritative record of what revenue has actually been collected and from whom. Salesforce is the revenue intelligence and sales execution layer—it holds the pipeline, forecasts, account relationships, and historical context that sales, finance, and executive teams use to make decisions. The gap between these two systems is a significant operational liability: sales representatives cannot see payment status on their accounts without leaving Salesforce, finance teams cannot correlate payment events to CRM pipeline stages without manual exports, and customer success teams have no automated signal when a key account's payment fails and churn risk spikes.

The Stripe-to-Salesforce integration closes this gap by converting Stripe payment and subscription lifecycle events into structured Salesforce record updates. A successful PaymentIntent creates or updates an Opportunity to Closed Won with the exact recognized revenue amount. A subscription cancellation triggers a workflow that alerts the assigned Customer Success Manager and logs a follow-up task. A failed invoice payment creates a Salesforce Task assigned to the account owner with the failure reason and a direct link to the Stripe customer portal. The result is a Salesforce instance that reflects actual payment reality in near-real-time rather than relying on sales reps to manually update deal stages after receiving payment confirmation emails.

Core Prerequisites

On the Stripe side, configure webhook endpoints in the Stripe Dashboard under Developers > Webhooks. Register your integration's listener URL and subscribe to the following events: payment_intent.succeeded for one-time charge confirmation, customer.subscription.created for new subscription activation, customer.subscription.updated for plan changes, upgrades, downgrades, and trial conversions, customer.subscription.deleted for cancellation events (both immediate and scheduled end-of-period cancellations), and invoice.payment_failed for dunning and churn-risk signaling. Pin the webhook endpoint to a specific Stripe API version to prevent payload schema drift from breaking your parsing logic. Each webhook delivery includes an X-Stripe-Signature header; your endpoint must verify this against your endpoint's whsec_ signing secret using HMAC-SHA256 before processing any payload. This verification is a hard security requirement, not an optional step.

For Stripe metadata hygiene—which is essential for reliable Salesforce mapping—establish a convention of storing salesforce_account_id, salesforce_opportunity_id, and salesforce_contact_id in the metadata object of Stripe Customer and Subscription records at the point of creation. When your application creates a Stripe Customer after a Salesforce Opportunity is created, immediately write the Opportunity ID and Account ID into the Stripe Customer's metadata. This bidirectional ID cross-referencing eliminates the need for brittle email-based lookups when matching Stripe events to Salesforce records.

On the Salesforce side, create a Connected App in Setup > App Manager with OAuth 2.0 enabled. The required OAuth scopes are api for full REST API access, refresh_token and offline_access for background process session continuity, and wave if your org uses Salesforce Analytics and you intend to push payment data to reporting datasets. The integration service account must have Create and Edit permissions on the Opportunity, Contact, Account, Task, and Activity objects. If your Salesforce org uses record types on the Opportunity object (e.g., separate record types for "New Business" vs. "Renewal"), your integration must resolve the correct RecordTypeId at configuration time by querying SELECT Id, Name FROM RecordType WHERE SObjectType = 'Opportunity' and storing the mapping in your integration config—attempting to create an Opportunity without a valid RecordTypeId in orgs that require it will return a validation error.

Top Enterprise Use Cases

The highest-impact use case is automated Closed Won opportunity management. When Stripe fires a payment_intent.succeeded event for a one-time charge, or a customer.subscription.created event for a new subscription, the integration locates the associated Salesforce Opportunity using the cross-referenced IDs stored in Stripe metadata, updates the StageName to "Closed Won," sets the CloseDate to today's date, and populates the Amount field with the exact payment amount from Stripe (normalized from cents to dollars). This eliminates the manual stage update that sales reps frequently forget or delay, which corrupts pipeline accuracy and forecast models.

Subscription lifecycle mirroring is the second critical use case. When a Stripe subscription transitions states—from trialing to active, from active to past_due, from active to canceled—the integration should write a corresponding Subscription_Status__c custom field value to the Salesforce Account record and log an Activity record describing the transition. This gives account teams a complete subscription history within Salesforce without requiring access to the Stripe Dashboard.

Failed payment and churn risk alerting is the third high-value use case. When Stripe fires invoice.payment_failed, the integration creates a high-priority Salesforce Task assigned to the Account owner with the subject "Payment Failed - Immediate Action Required," a description including the failed amount, failure reason code from Stripe (e.g., card_declined, insufficient_funds, expired_card), the number of previous failed attempts on this invoice, and the next retry date from Stripe's dunning schedule. This task creation ensures that account teams can proactively reach out to customers to resolve payment issues before Stripe's automatic subscription cancellation triggers.

Step-by-Step Implementation Guide

When your webhook listener receives a customer.subscription.created event from Stripe, after validating the X-Stripe-Signature header, you will parse a payload that includes the complete subscription object. The fields most critical for Salesforce mapping are:

{
  "id": "evt_1OHAxxxxxxxxx",
  "type": "customer.subscription.created",
  "data": {
    "object": {
      "id": "sub_1OHAxxxxxxxxx",
      "customer": "cus_Pxxxxxxxxx",
      "status": "active",
      "current_period_start": 1726358400,
      "current_period_end": 1728950400,
      "cancel_at_period_end": false,
      "items": {
        "data": [{
          "price": {
            "id": "price_1Oxxxxxxxxx",
            "unit_amount": 99900,
            "currency": "usd",
            "recurring": { "interval": "month" }
          },
          "quantity": 1
        }]
      },
      "metadata": {
        "salesforce_opportunity_id": "006Hs00000BkXyzIAF",
        "salesforce_account_id": "001Hs00000CmAbcIAE"
      }
    }
  }
}

Your middleware extracts metadata.salesforce_opportunity_id and issues a PATCH request to Salesforce's REST API: PATCH /services/data/v58.0/sobjects/Opportunity/006Hs00000BkXyzIAF. The request body updates the stage, amount, and writes the Stripe subscription ID to a custom field for cross-reference:

{
  "StageName": "Closed Won",
  "CloseDate": "2025-09-15",
  "Amount": 999.00,
  "Stripe_Subscription_ID__c": "sub_1OHAxxxxxxxxx",
  "Stripe_Customer_ID__c": "cus_Pxxxxxxxxx",
  "MRR__c": 999.00,
  "ARR__c": 11988.00
}

For invoice payment failure events, the payload's data.object.last_payment_error object contains the failure reason code. Extract data.object.last_payment_error.code (e.g., card_declined), data.object.amount_due (in cents), and data.object.next_payment_attempt (Unix timestamp). Resolve the Salesforce Account ID from the Stripe Customer's metadata. Create a Salesforce Task via POST /services/data/v58.0/sobjects/Task/:

{
  "WhatId": "001Hs00000CmAbcIAE",
  "OwnerId": "005Hs00000DnDefIAG",
  "Subject": "Payment Failed - Immediate Action Required",
  "Description": "Stripe invoice payment failed. Amount: $149.00. Reason: card_declined. Next retry: 2025-09-18. Subscription: sub_1OHAxxxxxxxxx.",
  "Priority": "High",
  "Status": "Not Started",
  "ActivityDate": "2025-09-16"
}

For Zapier, use the Stripe "New Event" trigger filtered to customer.subscription.created. Map the metadata.salesforce_opportunity_id field from the trigger output to the Salesforce "Update Record" action's Record ID field. Use a Stripe "Retrieve Customer" action step between the trigger and the Salesforce action to fetch any additional customer properties needed for Salesforce field population. For the payment failure path, create a separate Zap triggered by Stripe "New Event" filtered to invoice.payment_failed, with a Salesforce "Create Record" action creating the Task object with the failure context fields.

For Make, the "Watch Stripe Events" module supports event type filtering. Use a Router to direct customer.subscription.created, customer.subscription.updated, and invoice.payment_failed events to separate processing routes within the same scenario. For the subscription created route, a Salesforce "Update a Record" module patches the Opportunity using the ID from the Stripe event's metadata. For the payment failed route, a Salesforce "Create a Record" module creates the Task, with the Activity Date computed by Make's date function adding one day to the current date to set a next-business-day follow-up deadline.

Common Pitfalls & Troubleshooting

A 404 Not Found from Salesforce when attempting to PATCH an Opportunity using an ID from Stripe metadata indicates either that the metadata was never written during the original customer creation flow, or that the Salesforce record was deleted after the Stripe customer was created. Implement a fallback resolution strategy: if the metadata ID lookup fails, issue a Salesforce SOQL query searching for Opportunities where Stripe_Customer_ID__c = 'cus_Pxxxxxxxxx' to attempt resolution by the stored Stripe customer ID. If that also fails, log the event to a dead-letter queue for manual review rather than silently discarding it. Every unmatched Stripe payment event represents potential revenue that is not being reflected in your CRM.

A 400 Bad Request from Salesforce when creating the Opportunity update most frequently occurs because the StageName value you are attempting to set does not exist in the Opportunity Stage picklist for the record's record type. Salesforce stage picklists are configurable per record type; the "Closed Won" label may have been renamed by your Salesforce admin to "Won" or "Contract Executed." Query SELECT MasterLabel FROM OpportunityStage WHERE IsWon = true AND IsClosed = true to determine the correct "won" stage label for your org and store it in your integration configuration rather than hard-coding the generic "Closed Won" string.

Stripe webhook event ordering is not guaranteed. Under high load or retry conditions, a customer.subscription.updated event may arrive at your endpoint before the customer.subscription.created event for the same subscription. Build your integration to be order-agnostic by using Salesforce upsert operations (via external ID fields) rather than strict create/update sequences. Define Stripe_Subscription_ID__c as an External ID field in Salesforce and use the upsert endpoint PATCH /services/data/v58.0/sobjects/Opportunity/Stripe_Subscription_ID__c/{subId} to handle both create and update scenarios idempotently in a single API call.