Back to Blog
Salesforce

How to Create a PayPal REST API App for a Salesforce Integration

SG

Shivam Goel

Strategy & Growth Associate

11 min read - Jun 14, 2026

Why This Setup Exists

Every PayPal to Salesforce integration starts in the same place, and it is not Salesforce. Before a single Apex callout leaves your org, PayPal needs to know which application is calling, which merchant account it acts for, and which APIs it may touch. That lives in a REST API app on developer.paypal.com. Until it exists you have no client ID, no secret, and nothing for a Named Credential to point at.

Cloudsheer has shipped this across three client projects and the auth model was identical every time: OAuth 2.0 client_credentials, a client ID and secret exchanged at /v1/oauth2/token for a Bearer token. In Salesforce that maps onto an External Credential using the Client Credentials with Client Secret Flow, plus a Named Credential pointed at api-m.paypal.com. Inbound events arrive as webhooks verified with a webhook ID.

This is the zoomed-in first step of our technical guide at cloudsheer.com/integrations/paypal/technical-guide. Here we do only the credential and OAuth work, ending with a real Apex callout that returns 201.

What You Need Before You Start

  • A PayPal Business account with a confirmed email. A Personal account can build the whole sandbox integration and then get stuck at go-live, because live credentials need a verified Business or Premier account with completed identity and bank verification.
  • Access to https://developer.paypal.com via 'Log in to Dashboard' in the top right.
  • A Salesforce org where you can reach Setup > Named Credentials and create permission sets.
  • A public HTTPS endpoint on port 443 for webhooks - a Salesforce Site or Experience Cloud guest-user Apex REST class works. PayPal rejects localhost, so local development needs a tunnel such as ngrok.
  • A password manager. You handle a client secret in step 3 and it should never touch email, Slack, or a spreadsheet.
  • Timeline awareness if Payouts or live access is in scope. PayPal reviews Merchant API applications in roughly 24 to 72 hours.

Step 1: Log In and Flip the Environment Toggle

  • Click 'Apps & Credentials' in the left nav. Direct URLs: https://developer.paypal.com/dashboard/applications/sandbox and https://developer.paypal.com/dashboard/applications/live. Bookmark those - PayPal restyles labels often, the URL paths have stayed stable.
  • At the top of the page is a 'Sandbox' / 'Live' toggle. Flip it to Sandbox before you create anything. Apps, credentials, sandbox accounts, and webhooks are entirely separate per environment and nothing migrates.
  • Plan for two of everything: two client ID and secret pairs, two webhook IDs, and either two Salesforce External Credentials or one driven by Custom Metadata.

Missing this toggle is the most-reported PayPal integration failure. It silently mints credentials for the wrong environment, and the symptom only surfaces as a 401 invalid_client on your first token call.

Step 2: Create Your Sandbox Test Accounts

A Merchant app in sandbox attaches to a sandbox business account, so create accounts first at Testing Tools > Sandbox Accounts, or https://developer.paypal.com/dashboard/accounts.

  • Two default accounts already exist, following the pattern sb-xxxxxxx@business.example.com and sb-xxxxxxx@personal.example.com. They are usable as is.
  • To add your own, click 'Create account', set Account Type to 'Business' (the merchant receiving payment) or 'Personal' (the buyer), pick a Country, click 'Create Account'. Use 'Create Custom Account' to control balance, card, or email.
  • For a login, open the '...' menu on the row and choose 'View/Edit account'. The System-Generated Password is shown there and can be changed.
  • Log in at https://www.sandbox.paypal.com, not paypal.com. Sandbox logins fail confusingly on the production login page.

Step 3: Create the REST API App and Copy the Credentials

Back on Apps & Credentials, find the 'REST API apps' section and click 'Create App' in the upper right. New accounts have a 'Default Application' already, but a named app is easier to audit.

  • Enter an App Name that identifies org and environment, for example Salesforce_Checkout_Sandbox.
  • Set Type to 'Merchant'. That is correct when you process payments as the merchant, which covers almost every Salesforce integration. 'Platform' is for multiparty marketplaces.
  • In sandbox, pick the 'Sandbox Account' from the dropdown and select the business account from step 2, so test transactions land in a seller account you can log into. Click 'Create App'.
  • The app page shows 'Client ID' in full. Click 'Show' next to 'Secret' to reveal the client secret. Copy both into a password manager immediately, and treat the secret like a production password from this second onward: no email, no Slack, no spreadsheet.
  • Unlike AWS or Azure, the secret is re-viewable via 'Show' later. Rotation via 'Generate New Secret' is a hard cutover: update Salesforce, verify a callout, then revoke the old secret.

Step 4: Enable the Features That Grant Your Scopes

You never pass a scope parameter to /v1/oauth2/token. PayPal derives scopes server-side from the app's enabled features and the merchant's permissions, then returns them in the token response's 'scope' field.

  • On the app page scroll to 'Sandbox App Settings' (or 'Live App Settings') and find the 'Features' panel. Some boxes are pre-checked, typically Accept payments.
  • Tick what you need and click 'Save' on that row. This panel holds Payouts, Invoicing, Subscriptions, Vault, Transaction Search, Log in with PayPal, and dispute management. PayPal restyles it frequently, so read the labels on screen rather than trusting any screenshot, including ours.
  • If a call later returns 403 NOT_AUTHORIZED, the cause is usually an unchecked box here. Fix it, then request a brand new token - an issued token keeps its old scope set for its full lifetime.
  • A 'Return URL' field appears only under Log in with PayPal's 'Advanced Options'. A client_credentials integration never uses it, because no browser round-trip occurs. Leave that feature off unless you need PayPal as an identity provider, since all its scopes require review before working in live.

Step 5: Create the Webhook and Copy the Webhook ID

Webhooks live on the same app detail page. Scroll to the 'Webhooks' section and click 'Add Webhook'.

  • Enter the 'Webhook URL', for example https://payments.yourcompany.com/services/apexrest/paypal/events. If PayPal answers 'Invalid URL. Please enter a valid URL starting with http:// or https://', it could not reach the host - that is the localhost rejection.
  • Under 'Event types', tick 'All events' or select specific ones. All events is safer, because teams forget to add events later. The checkout core set is CHECKOUT.ORDER.APPROVED, PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED, PAYMENT.CAPTURE.REFUNDED.
  • Click 'Save'. The webhook is listed with its 'Webhook ID', like 8SQ12345AB678901C. Copy it into a Custom Metadata record. It is a required body field for /v1/notifications/verify-webhook-signature, it is environment-specific, and it is not your Client ID. Pasting the Client ID there is a routine cause of verification_status FAILURE.
  • Maximum 10 webhook URLs per app, and the ID is re-readable any time.
  • Test before real events flow: Testing Tools > Webhooks Simulator at https://developer.paypal.com/dashboard/webhooksSimulator. Paste the URL, pick an event, click 'Send Test'.
  • Once live, 'Webhooks Events' at https://developer.paypal.com/dashboard/webhooks shows delivery history, the HTTP status your listener returned, and a manual 'Resend'.

Step 6: Wire the Credentials Into Salesforce

Go to Setup > Named Credentials, open the 'External Credentials' tab, click New. There is no Auth Provider here, because client_credentials involves no browser redirect.

  • Authentication Protocol = 'OAuth 2.0'. Authentication Flow Type = 'Client Credentials with Client Secret Flow'.
  • Identity Provider URL = https://api-m.sandbox.paypal.com/v1/oauth2/token, or https://api-m.paypal.com/v1/oauth2/token for live. Leave Scope blank, PayPal does not accept one. Save.
  • Under Principals click New. Parameter Name such as PayPal_Sandbox, Identity Type = 'Named Principal', then put Client ID and Client Secret into the Authentication Parameters.
  • Add a Permission Set Mapping with Identity Type 'Named Principal', mapping a permission set your integration user holds. Skip this and the callout fails even though the credential looks perfectly configured.
  • Named Credentials tab > New. URL = https://api-m.sandbox.paypal.com, base host only, no path. Select the External Credential and enable 'Generate Authorization Header' so Salesforce injects 'Bearer <token>' for you.
  • No Remote Site Setting is needed, and do not write Apex token-caching code. Salesforce caches the token and re-fetches on expiry. PayPal commonly returns expires_in of 32400 seconds, but the documented range is 15 minutes to nine hours depending on scopes, so never hardcode a lifetime. There is no refresh token here.
  • If your listener runs as a Site guest user and also calls out, grant that profile object permissions on 'User External Credentials'.

Step 7: Smoke Test With a Real Callout

Open Developer Console > Debug > Open Execute Anonymous Window and create a real order. This exercises the token exchange, the Named Credential, the permission set mapping, and the Accept payments feature at once.

  • Set the endpoint by credential name, not raw URL: req.setEndpoint('callout:PayPal_Sandbox/v2/checkout/orders'), method POST, Content-Type application/json.
  • Send a minimal body: intent CAPTURE and one purchase unit with amount currency_code USD, value 10.00. Add a unique PayPal-Request-Id header so PayPal deduplicates if you retry.
  • Success is HTTP 201 with a body containing an id like 5O190127TN364715T, status CREATED, and a links array with an approve link.
  • Then test inbound: fire a simulator event and confirm your listener captured paypal-transmission-id, paypal-transmission-time, paypal-transmission-sig, paypal-cert-url, and paypal-auth-algo, and posted them with your webhook_id to /v1/notifications/verify-webhook-signature for verification_status SUCCESS.

Step 8: Promote the Same Setup to Live

  • Flip the toggle to 'Live' and click 'Create App' again. This mints a separate live app with its own Client ID and Secret. Nothing carries over.
  • Re-tick the Features and re-create the webhook against your production listener. Copy the new webhook ID into production Custom Metadata.
  • Payouts needs a second, account-level approval independent of the app checkbox: 'My Account' > 'Payouts' > 'Enable', or from the business account 'Pay & Get Paid' > 'Payouts'. Payouts works freely in sandbox, so this wall only appears at go-live.
  • In Salesforce, change the Named Credential host from api-m.sandbox.paypal.com to api-m.paypal.com and load the live secret. Older docs reference api.paypal.com without the -m; api-m is current.

Common Errors and How to Fix Them

  • HTTP 401 invalid_client, 'Client Authentication failed': sandbox credentials sent to api-m.paypal.com or the reverse. Confirm the toggle you copied from matches the Named Credential host, and check the secret was not truncated on paste.
  • HTTP 400 INVALID_REQUEST, 'Request is not well-formed': the token body was not sent as application/x-www-form-urlencoded with grant_type=client_credentials.
  • HTTP 403 NOT_AUTHORIZED: a Features checkbox is off, or the merchant lacks the permission, classic for Payouts before approval. Fix the feature, then force a fresh token, because Salesforce is caching one with the stale scope set.
  • HTTP 404 INVALID_RESOURCE_ID: a sandbox order or capture ID used against live, or a resource used outside its lifecycle, such as capturing an order that is not APPROVED.
  • HTTP 422 UNPROCESSABLE_ENTITY: valid syntax, failed business rules. Read the details array's 'issue' field, such as ORDER_ALREADY_CAPTURED or INSTRUMENT_DECLINED.
  • HTTP 429 RATE_LIMIT_REACHED: almost always minting a token per call. Let the External Credential cache it and add backoff.
  • verification_status FAILURE: wrong webhook_id, the event body re-serialized instead of passed byte-for-byte, or headers read case-sensitively.
  • A no-valid-principal Salesforce error despite correct credentials: the Permission Set Mapping is missing, or the running user lacks that permission set.

Security Checklist Before You Ship

  • The client secret lives in exactly two places: your password manager and the External Credential Principal. Never in Apex, a Custom Setting, or version control.
  • Verify every inbound webhook signature before acting on the payload. Your listener URL is public by design, and skipping verification leaves no way to prove the sender was PayPal.
  • Make the listener idempotent, keyed on event id. PayPal delivers at-least-once and out of order.
  • Separate live and sandbox credentials at the metadata level so a sandbox refresh cannot drag production keys into a scratch org.
  • Scope the integration user tightly. The mapped permission set should grant the callout and nothing else.
  • Rotate the secret deliberately - PayPal never forces it. Generate, update Salesforce, confirm a 201, then revoke the old one.
  • If the listener runs as a guest user, review that profile's permissions with your security team.

Where This Fits in the Full PayPal Build

Everything above is the foundation layer. On top of it sits the commerce logic: creating orders from Opportunities or Cases, capturing payments, mapping capture IDs onto Salesforce records, handling refunds and disputes, reconciling fees, and deciding whether subscriptions live in PayPal or your own billing objects. Sandbox webhook delivery is slower and flakier than live, so never tune retry timeouts against sandbox behaviour.

The full architecture, Apex patterns, and reconciliation design are documented at cloudsheer.com/integrations/paypal/technical-guide. If you use Claude Code, we publish a free skill carrying this setup and the downstream patterns as executable context: npx skills add shivamgoel-cloudsheer/Claude-skills --skill paypal-salesforce

How Cloudsheer Can Help

We have shipped this integration across three client projects, and the credential work is rarely what stalls a build. What stalls builds is the approval calendar: Payouts enablement taking 72 hours, live Merchant API review landing mid-sprint, a Personal account discovered at go-live when a verified Business account was required. Cloudsheer sequences those approvals against your release plan so the waiting happens in parallel with development.

We also build the parts this article skips: an idempotent, signature-verifying webhook listener, a reconciliation model that survives out-of-order events, and an environment strategy where sandbox and live credentials cannot cross. If you want a second opinion on your architecture before the first line of Apex, book a free discovery call at cal.com/cloudsheer-consulting/30min

Want to see how this applies to your business?

Book a free 30-minute call. We will walk through your specific use case and show you what's possible.

Book Free Discovery Call
Ask me anything