
API integration for PayPal
We build your PayPal connector
Your application calls the PayPal API to take payments, run subscriptions and handle disputes without the buyer typing a card number.
- Senior product team
- payment integrations in production
- from scoping to monitoring
What does the PayPal API provide and why integrate it as a payment method?
PayPal is the most widely used digital wallet in the world, particularly appreciated by online shoppers for its simplicity and buyer protection. Its API lets your application take a one-time payment, manage subscriptions, trigger a refund, and be notified of each payment event. You integrate it to offer PayPal alongside card payment in a checkout, or to collect payments in markets where PayPal is the dominant payment method. It is also the reference solution for managing buyer disputes directly from your back office.
What our clients build on the PayPal API
Ecommerce checkout with a real capture
The browser return does not open the order. PAYMENT.CAPTURE.COMPLETED triggers fulfilment, not a page callback.
SaaS subscription driven by events
Activation, suspension and failed payment open or close access. No boolean kept by hand in two systems.
Dispute queue in the back office
CUSTOMER.DISPUTE.CREATED feeds a queue with a deadline and attachments, instead of an email found too late.
Collecting for several merchants
PayPal-Auth-Assertion lets you act for a merchant without one token per account, with the partner BN code.
What it changes in how you take payment
Engineering in service of a measurable outcome: fewer drop-offs, accurate access, disputes handled.
Fewer drop-offs at payment
The buyer pays with an account they already have. No 16-digit entry on mobile, often the step that closes the cart.
Access aligned with payment
The PayPal subscription opens, suspends and closes the right. You stop matching an export against an internal table.
Disputes leave the support inbox
Each chargeback lands in your tool, with a deadline. You reply on time instead of discovering it on the debit.
Books that actually balance
Captures and refunds carry an order reference. Matching stops being an export read by hand.
How we ship your PayPal connector
Scoping
Checkout, subscription or platform, 3DS rule, which events matter. We settle the model before writing a line of code.
Development
Typed connector, RSA verification on the raw body, PayPal-Request-Id derived from the business intent, retry queue.
Acceptance testing
PayPal-Mock-Response for business errors, sandbox for the signature, simulator only for delivery mechanics.
Monitoring
Alerts on unprocessed events, a capture log, a health dashboard. You know a flow is broken before your customers do.
What the PayPal API allows
- Orders and capture
- Create an order, buyer approval, capture. Business state is read from the capture, not from the browser return.
- Subscriptions and plans
- Plans, activation, suspension, reactivation, expiry and failed payment. Each transition is a named event.
- Disputes and refunds
- A queue opened on CUSTOMER.DISPUTE.CREATED, refund of a capture with PayPal-Request-Id to avoid a double credit.
- Webhooks and 3DS
- Up to 10 URLs per application, SCA_WHEN_REQUIRED or SCA_ALWAYS, and liability_shift as a capture rule, not a forgotten field.
The vocabulary of the PayPal API
- PayPal-Request-Id
- Idempotency header on some POSTs, remembered for up to 45 days on the captured-payment refund example. It is not universal: the API reference says so endpoint by endpoint.
- paypal-transmission-sig
- Base64 webhook signature. The signed message is transmissionId | timeStamp | webhookId | crc32, where crc32 is the raw-body CRC32 in decimal, verified with SHA256withRSA.
- webhookId
- The subscription identifier, in neither the header nor the body. Without persisting it when the URL is created, signature verification is impossible.
- liability_shift
- The 3DS result that says whether chargeback risk moved to the issuer. It is a business rule (capture or not), not a technical field to ignore.
- PayPal-Auth-Assertion
- JWT identifying the merchant you act for. PayPal recommends payer_id over email, and an unsigned JWT (alg none) for this case.
- SCA_WHEN_REQUIRED
- Default verification.method: PayPal triggers 3DS only when local rules require it. SCA_ALWAYS attempts it on every card.
The real constraints of the PayPal API
The signature is not an HMAC
Rebuild transmissionId|timeStamp|webhookId|crc32 on the raw body, CRC32 in decimal, then RSA-SHA256 with the certificate from paypal-cert-url. Parse-then-reserialize fails verification.
The simulator does not verify
On simulated events the identifier is the literal string WEBHOOK_ID, and POST /v1/notifications/verify-webhook-signature is not supported. Many teams think their code is broken.
Up to 25 retries over 3 days
Without a 2xx, PayPal retries up to 25 times over 3 days, then moves to Failed. A webhook down over a weekend produces duplicates to dedupe, not a silent loss.
Idempotency and rate limits to measure
PayPal-Request-Id is not available everywhere. No numeric rate limit is published: a processing rate is measured in test, then watched, not inferred from a doc.
Orders or Subscriptions?
Two APIs for two questions. The right choice depends on the money's lifecycle, not on the logo on the button.
| Criterion | OrdersOne-off payment | SubscriptionsRecurring |
|---|---|---|
| Business object | An order, then a capture | A plan, then a subscription |
| Event that closes it | PAYMENT.CAPTURE.COMPLETED | BILLING.SUBSCRIPTION.ACTIVATED |
| What failure looks like | Decline or capture denied | BILLING.SUBSCRIPTION.PAYMENT.FAILED |
| Product access | Opened on capture | Opened, suspended, reopened on events |
| Legacy to avoid | Deprecated 3DS field location | Billing Agreements, deprecated |
| Platform case | PayPal-Auth-Assertion, BN code | Same header, plus the subscription cycle |
| The right case | Cart, order, one-off service | SaaS, membership, access over time |
The two combine: a first Orders payment at signup, then Subscriptions for renewal. It is a scoping trade-off, not a permanent choice.
What we measure on a PayPal integration
The other payment APIs
If PayPal is not the right fit for your model, these options are worth discussing during scoping.
PayPalWe build your PayPal connectorThis page
StripeThe most complete: Connect, Billing, entitlements, when payment is the core of the product.
SumUpIn-person collection, when the sale closes on a terminal.
GoCardlessRecurring SEPA Direct Debit, with no card that expires on B2B amounts.
MollieWe build your Mollie connectorWe combine PayPal with
The stack that surrounds PayPal on our projects.
PayPal integration: your questions
Three steps. First obtain a token via POST /v1/oauth2/token with client_credentials, and read the scopes actually granted before the first business call. Then build the server-side connector: creating Orders or Subscriptions, with PayPal-Request-Id derived from the business intent on POSTs that support it. Finally handle webhooks: persist the webhookId when the URL is created, verify the RSA signature on the raw body (CRC32 in decimal), return 200, process in a queue. The sensitive part is not the API call, it is verification that is not HMAC, and the fact that PayPal retries up to 25 times over 3 days.
It depends on the model: an Orders checkout with capture is shorter than a subscription with access control, a dispute queue and a platform case. The hard part is almost never the PayPal button, it is the RSA signature, the simulator that does not verify, and webhook duplicates. A first useful flow ships in two to three weeks. A full chain with subscriptions and disputes is closer to six to eight weeks. We scope the perimeter up front and give a firm estimate before we start.
Orders collects on an order: create, approve, capture. The event that closes the flow is PAYMENT.CAPTURE.COMPLETED. Subscriptions manages a cycle over time: plan, activation, suspension, failed payment, with BILLING.SUBSCRIPTION.* events. REST Billing Agreements are deprecated in favour of Subscriptions. Many projects need both: a first Orders payment, then a subscription. The data model has to be designed for both at scoping, not bolted on afterwards.
Not with HMAC, unlike Stripe or GoCardless. Rebuild transmissionId|timeStamp|webhookId|crc32, where crc32 is the raw HTTP body CRC32 expressed in decimal. paypal-transmission-sig is verified with SHA256withRSA using the certificate downloaded from paypal-cert-url, after checking the host. The webhookId comes from the subscription configuration, not from the message. The simulator cannot be verified via POST /v1/notifications/verify-webhook-signature: to test the crypto, you need a real sandbox event. Under Express or NestJS, the webhook route needs the raw body, not reserialized JSON.
Yes, and it is a frequent request. We build the flow that pushes captures and refunds with the order reference (supplementary_data.related_ids.order_id on the capture), then a periodic consistency check between what PayPal exposes and what your books record, with an alert on gaps rather than a silent fix. An accounting entry is not repaired behind the accountant's back. Manual matching of a PayPal export disappears once the reference is carried end to end.
A PayPal integration project?
Let's talk. 30 minutes to scope your checkout or subscriptions, check what the API actually allows and tell you honestly what is feasible.
Discuss my PayPal project