Documentation
Connectors
Available

PayPal

Connect PayPal to FloPay and configure checkout and webhook handling.

Overview

FloPay supports two PayPal paths and selects one from the checkout session response:

PathWhen it rendersBest fit
Direct PayPalgateways.paypal is present because the client has an active PayPal gateway.New integrations and buyers using Facebook, Meta, or Instagram in-app browsers.
PayPal via Stripegateways.paypal is absent or null.Existing Stripe-only integrations.

The paths are mutually exclusive per session. PayPal never renders twice, and FloPayCheckout switches automatically without user-agent checks or client-specific props.

showPayPal is the SDK-level toggle for the entire PayPal gateway. It controls both the direct and Stripe-rendered paths and defaults to true.

<FloPayCheckout sessionId={sessionId} showPayPal={false} onComplete={handleSuccess} />

Direct PayPal loads the PayPal JS SDK with the active gateway's client ID and environment. It avoids the popup and return-tab problems that affect Stripe-rendered PayPal inside Facebook, Meta, and Instagram in-app browsers.

What you can do

  • Offer direct PayPal for one-time purchases and subscriptions in FloPay Checkout.
  • Process direct PayPal refunds and reuse vaulted PayPal payment tokens for eligible follow-up purchases.
  • Verify PayPal events and receive provider-neutral FloPay webhook events for payment and subscription changes.

Who this is for

This connector is for merchants that want PayPal to appear as a direct checkout option. FloPay client owners and admins can configure the gateway. Developers can integrate it through the SDK or current checkout-session response.

Prerequisites

  • A PayPal developer account with access to create REST API apps and webhooks.
  • A FloPay client where you are an owner or admin.
  • An active, user-owned FloPay API token if you configure the gateway through the REST API.
  • Separate PayPal apps for sandbox and live environments, with the required product and Disputes API access.

Setup

Create one gateway record per environment. A stage gateway uses a PayPal sandbox app. A production gateway uses a PayPal live app. Moving to production requires a separate record.

Client owners and admins can manage gateways through Settings > Gateways or the settings/gateways API. Client Basic authentication uses the client identifier as the username and an active user-owned API token as the password. Members cannot manage gateways, and all operations are scoped to the authenticated client.

The setup sequence uses POST /v1/settings/gateways, PATCH /v1/settings/gateways/:id, POST /v1/settings/gateways/:id/test, and POST /v1/settings/gateways/:id/activate.

Gateway lifecycle

StateMeaningAvailable actions
draftNew or incomplete, and not used for payments.Edit, test, activate
activeComplete, validated, and eligible for checkout.Edit settings, replace credentials, test, deactivate
inactiveComplete but deactivated.Edit, test, reactivate

Every gateway begins as a draft. Activation requires complete credentials and a PayPal validation. Deactivation keeps the record but removes it from new sessions. Reactivation validates it again. PayPal app identity and environment are immutable after the first successful validation.

1. Create sandbox and live apps

In the PayPal developer dashboard, create a REST API app for each required environment. Enable Orders v2, Subscriptions, Vault, Webhooks, and Disputes API access in both sandbox and live.

Without Disputes API access in both sandbox and live, FloPay cannot classify v2 capture reversals from dispute evidence. Those reversals fall back to refunds and are surfaced for review.

2. Register the webhook

On the matching PayPal app, register the callback URL shown on the gateway's Webhook setup card:

https://<flopay-webhook-host>/webhooks/paypal

On the same app, subscribe to the 16 PayPal events listed under Webhook events. PayPal returns a webhook ID after registration.

The subscription must include the reversal signals used for financial classification:

  • PAYMENT.SALE.REVERSED
  • PAYMENT.CAPTURE.REVERSED

3. Save credentials

FloPay fieldPayPal value
providerTypepaypal
publishableKeyREST app client ID
secretKeyREST app client secret, write-only
webhookSigningSecretPayPal webhook ID, write-only
environmentstage for sandbox or production for live

webhookSigningSecret stores the PayPal webhook ID, not an HMAC secret. FloPay passes it to PayPal's hosted /v1/notifications/verify-webhook-signature endpoint. Read responses expose only configured-state metadata for write-only fields. Omitted credential fields keep their stored values during an update.

curl -X POST https://api.flopay.com/v1/settings/gateways \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "PayPal live",
    "providerType": "paypal",
    "environment": "production",
    "currency": "USD"
  }'

curl -X PATCH https://api.flopay.com/v1/settings/gateways/GATEWAY_ID \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "publishableKey": "PAYPAL_CLIENT_ID",
    "secretKey": "PAYPAL_CLIENT_SECRET",
    "webhookSigningSecret": "PAYPAL_WEBHOOK_ID"
  }'

curl -X POST https://api.flopay.com/v1/settings/gateways/GATEWAY_ID/test \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN"

curl -X POST https://api.flopay.com/v1/settings/gateways/GATEWAY_ID/activate \
  -u "$FLOPAY_CLIENT_ID:$FLOPAY_API_TOKEN"

FloPay validates the app credentials and webhook ID before saving or activating. A failed update is atomic and leaves the previous configuration untouched. Once active, new checkout sessions include gateways.paypal.

Supported features

FeatureSupport
One-time paymentsPayPal Orders v2 approval and capture
Recurring billingPayPal subscriptions, renewals, pauses, resumes, and cancellations
RefundsRefunds against direct PayPal capture IDs
Vaulted payment methodsToken storage and reuse after a direct PayPal purchase
WebhooksHosted signature verification and normalized FloPay events
ReversalsRefund or chargeback classification from PayPal evidence

Direct PayPal

Direct PayPal renders when session.gateways.paypal is present. The SDK loads PayPal using that gateway's publishableKey and environment. When the entry is missing, the SDK uses the Stripe-rendered fallback instead.

API shape

The session response has this shape:

{
  "id": "sess_abc123",
  "amount": 4999,
  "currency": "usd",
  "gateways": {
    "stripe": {
      "publishableKey": "pk_live_xxx",
      "environment": "production"
    },
    "paypal": {
      "publishableKey": "PAYPAL_CLIENT_ID",
      "environment": "production"
    }
  }
}

The old top-level gateway and gatewayData.paypalPublishableKey fields have been replaced by the gateways map. The FloPay SDK consumes the current shape automatically. Direct API consumers must update their session reader.

SDK integration

FloPayCheckout and SplitCardForm consume session.gateways and select the path automatically:

import { FloPayCheckout } from '@flopay/react';

export function Checkout({ sessionId }: { sessionId: string }) {
  return (
    <FloPayCheckout
      sessionId={sessionId}
      onComplete={(result) => {
        if (result.status === 'succeeded') window.location.href = '/success';
      }}
      onError={(error) => console.error(error.message)}
    />
  );
}

Each PayPal gateway has its own loader, so sandbox and live credentials do not leak between environments.

Lifecycle

One-time purchases

Direct one-time purchases use the PayPal Orders v2 API:

  1. The buyer selects PayPal.
  2. FloPay creates an order with line items and totals.
  3. PayPal opens the approval flow.
  4. FloPay captures the approved order against the checkout session.
  5. PAYMENT.CAPTURE.COMPLETED maps to item.purchased and invoice.paid.

Subscriptions

Subscriptions use PayPal's Subscriptions API. PayPal drives the billing cycle.

ActionPayPal operationFlo outbound event
SignupCreate and approve a subscriptionsubscription.created, then subscription.renewed when paid
RenewalNative PayPal cyclesubscription.renewed
PausePOST /v1/billing/subscriptions/{id}/suspendsubscription.paused
ResumePOST /v1/billing/subscriptions/{id}/activatesubscription.resumed
CancelPOST /v1/billing/subscriptions/{id}/cancelsubscription.cancelled
ExpirePayPal lifecycle endsubscription.expired
Failed paymentPayPal recurring failureinvoice.overdue

FloPay does not issue off-session retries for PayPal subscriptions. BILLING.SUBSCRIPTION.PAYMENT.FAILED arrives after PayPal has exhausted its retry window.

Refunds

Refund a direct capture against its PayPal capture ID. FloPay calls POST /v2/payments/captures/{capture_id}/refund; the resulting PAYMENT.CAPTURE.REFUNDED maps to item.refunded.

Vaulted token reuse

After a first direct PayPal purchase, FloPay can store the vaulted token on user_payment_method with type='paypal'. An upsell can use the existing automatic-payment-button or saved-payment-method surface without rendering the PayPal button again. VAULT.PAYMENT-TOKEN.CREATED stores the method, and VAULT.PAYMENT-TOKEN.DELETED removes it.

Webhook events

Direct PayPal events are normalized into the same outbound subscription.*, item.*, and invoice.* families as other gateways.

FloPay subscribes to the following 16 PayPal webhook events.

PayPal eventPurpose
CHECKOUT.ORDER.APPROVEDThe buyer approved a one-time order.
PAYMENT.CAPTURE.COMPLETEDA capture succeeded.
PAYMENT.CAPTURE.DENIEDPayPal denied a capture.
PAYMENT.CAPTURE.PENDINGA capture is under review.
PAYMENT.CAPTURE.REFUNDEDA capture was refunded.
PAYMENT.CAPTURE.REVERSEDA capture was reversed and requires classification.
PAYMENT.SALE.REVERSEDA subscription Sale was reversed and carries a reason code.
BILLING.SUBSCRIPTION.CREATEDA subscription was created.
BILLING.SUBSCRIPTION.ACTIVATEDA subscription became active.
BILLING.SUBSCRIPTION.UPDATEDSubscription details changed.
BILLING.SUBSCRIPTION.CANCELLEDA subscription was cancelled.
BILLING.SUBSCRIPTION.SUSPENDEDA subscription was paused.
BILLING.SUBSCRIPTION.EXPIREDA subscription reached its end.
BILLING.SUBSCRIPTION.PAYMENT.FAILEDPayPal exhausted retries for a recurring charge.
VAULT.PAYMENT-TOKEN.CREATEDA vaulted PayPal token was created.
VAULT.PAYMENT-TOKEN.DELETEDA vaulted PayPal token was removed.

Signature verification

FloPay sends the webhook ID and delivery headers to PayPal's /v1/notifications/verify-webhook-signature endpoint before processing an event. Local certificate verification is not used in this phase.

Inbound to outbound mapping

PayPal eventOutbound Flo eventNotes
CHECKOUT.ORDER.APPROVED(no outbound)Internal trigger to capture the order.
PAYMENT.CAPTURE.COMPLETEDitem.purchased, invoice.paidIncludes a successful first subscription cycle.
PAYMENT.CAPTURE.DENIEDinvoice.overdueThe invoice remains collectible.
PAYMENT.CAPTURE.PENDING(no outbound)FloPay waits for a final capture state.
PAYMENT.CAPTURE.REFUNDEDitem.refundedOne-to-one with the capture refund.
PAYMENT.CAPTURE.REVERSEDinvoice.updated, plus either item.charged_back or item.refundedFloPay queries the Disputes API before selecting one path.
PAYMENT.SALE.REVERSEDinvoice.updated, plus either item.charged_back or item.refunded, except for chargeback_reimbursementA chargeback_reimbursement restores the invoice without any item event.
BILLING.SUBSCRIPTION.CREATEDsubscription.createdA subscription was created.
BILLING.SUBSCRIPTION.ACTIVATEDsubscription.created or subscription.reactivatedDepends on prior FloPay state.
BILLING.SUBSCRIPTION.UPDATEDsubscription.updatedDetails changed.
BILLING.SUBSCRIPTION.CANCELLEDsubscription.cancelledThe subscription was cancelled.
BILLING.SUBSCRIPTION.SUSPENDEDsubscription.pausedThe subscription was paused.
BILLING.SUBSCRIPTION.EXPIREDsubscription.expiredThe subscription ended.
BILLING.SUBSCRIPTION.PAYMENT.FAILEDinvoice.overduePayPal exhausted its retry window, so FloPay does not retry.
VAULT.PAYMENT-TOKEN.CREATED(no outbound)FloPay stores the token for upsell reuse.
VAULT.PAYMENT-TOKEN.DELETED(no outbound)FloPay removes the stored token.

Reversal classification

PAYMENT.SALE.REVERSED

PayPal v1 Sale reversal reasons are trimmed, compared case-insensitively, and normalized so hyphens and underscores are equivalent.

ClassificationNormalized reason_code values
Chargebackchargeback, chargeback_reimbursement, chargeback_settlement, unauthorized_spoof, buyer_complaint, unauthorized_claim, guarantee
Refundrefund, admin_reversal, admin_fraud_reversal, adjustment_reversal

other, an unrecognised value, or an absent reason falls back to a refund and is surfaced for review. The first classification is kept for the provider reversal identity, so a replay cannot become both a refund and a chargeback.

chargeback_reimbursement restores the invoice through invoice.updated only, with no second item.charged_back and no item.refunded.

PAYMENT.CAPTURE.REVERSED

PayPal v2 PAYMENT.CAPTURE.REVERSED carries no reason code. FloPay queries the PayPal Disputes API by original capture ID. A recognized dispute at CHARGEBACK, PRE_ARBITRATION, or ARBITRATION is positive chargeback evidence.

A missing capture ID or client, empty or malformed response, failed API request, or otherwise unclassifiable evidence uses the fallback refund path and is surfaced for review. FloPay then freezes that classification for the provider reversal identity.

Sandbox testing

Provision a stage gateway for the PayPal sandbox, then verify:

  • One-time purchase: complete payment and receive item.purchased.
  • Subscription signup: receive subscription.created and a paid-cycle renewal.
  • Recurring renewal: use accelerated sandbox billing and receive subscription.renewed.
  • Pause, resume, and cancel: confirm each subscription transition.
  • Refund: refund a capture and receive item.refunded.
  • Vaulted upsell: reuse a saved token without showing the button again.
  • Meta in-app browser: complete checkout from a Facebook or Instagram mobile surface.
  • Failed renewal: receive invoice.overdue after PayPal finishes retrying.
  • Webhook timeline: confirm every inbound event was processed without pending retries.

Migration

  • Existing Stripe-rendered PayPal clients continue unchanged while gateways.paypal is absent.
  • Opt in to direct PayPal by creating and activating a PayPal gateway. New sessions then include gateways.paypal.
  • Update direct session readers from the retired gateway and gatewayData fields to the gateways map. SDK users do not need to change their reader.
  • Rolling back means deactivating the PayPal gateway. New sessions omit gateways.paypal and return to the Stripe path.

Limitations

  • Direct PayPal requires separate sandbox and live apps and gateway records.
  • FloPay relies on PayPal's hosted webhook verification endpoint in this phase.
  • Unclassifiable capture reversals fall back to refund classification and require review.
  • PayPal owns recurring retry timing. FloPay does not add off-session retries.
  • Stripe-rendered PayPal remains unreliable in Facebook, Meta, and Instagram in-app browsers.

On this page