FloPayFloPay
GatewaysPayPal

Webhooks

Inbound PayPal events FloPay subscribes to and how they map to outbound Flo webhooks.

PayPal Events

When a client provisions a direct PayPal gateway, FloPay subscribes to a fixed set of PayPal webhook events at the gateway level and normalizes them into the existing outbound Flo webhook families.

Direct PayPal events arrive at your client webhook endpoint as the provider-neutral subscription.*, item.*, and invoice.* events documented in Events, including item.charged_back. This page is reference material for what happens upstream — your handler code does not need to branch on gateway. The outbound payload shape is identical regardless of which gateway produced the underlying event.

Subscribed PayPal Events

FloPay subscribes to the following 16 PayPal webhook events. These are configured on the PayPal app you provision during direct PayPal setup.

PayPal eventPurpose
CHECKOUT.ORDER.APPROVEDBuyer approved a one-time order in the PayPal flow.
PAYMENT.CAPTURE.COMPLETEDOne-time capture succeeded.
PAYMENT.CAPTURE.DENIEDOne-time capture was denied by PayPal.
PAYMENT.CAPTURE.PENDINGCapture is pending PayPal review.
PAYMENT.CAPTURE.REFUNDEDA previous capture was refunded.
PAYMENT.CAPTURE.REVERSEDA previous capture was reversed (chargeback / dispute).
PAYMENT.SALE.REVERSEDA subscription Sale was reversed and needs reason-code classification.
BILLING.SUBSCRIPTION.CREATEDSubscription created in PayPal.
BILLING.SUBSCRIPTION.ACTIVATEDSubscription transitioned to active.
BILLING.SUBSCRIPTION.UPDATEDSubscription details changed (plan, quantity, etc.).
BILLING.SUBSCRIPTION.CANCELLEDSubscription was cancelled.
BILLING.SUBSCRIPTION.SUSPENDEDSubscription was suspended (paused).
BILLING.SUBSCRIPTION.EXPIREDSubscription reached its end and was not renewed.
BILLING.SUBSCRIPTION.PAYMENT.FAILEDA recurring charge failed after PayPal exhausted retries.
VAULT.PAYMENT-TOKEN.CREATEDA vaulted PayPal payment token was created — enables saved-payment upsells.
VAULT.PAYMENT-TOKEN.DELETEDA vaulted PayPal payment token was removed.

Signature Verification

FloPay verifies each delivery by calling PayPal's hosted /v1/notifications/verify-webhook-signature endpoint with the webhook id stored in the gateway record's webhook_signing_secret column. Local certificate-based verification is deferred — every inbound PayPal event hits PayPal's verification endpoint before it is processed.

Inbound → Outbound Mapping

PayPal eventOutbound Flo eventNotes
CHECKOUT.ORDER.APPROVED(no outbound)Internal trigger to capture the order.
PAYMENT.CAPTURE.COMPLETEDitem.purchased, invoice.paidEmitted on successful one-time capture, including the first cycle of a subscription.
PAYMENT.CAPTURE.DENIEDinvoice.overdueThe invoice stays collectible; PayPal denied this specific capture.
PAYMENT.CAPTURE.PENDING(no outbound)FloPay holds processing until the capture resolves to completed, denied, or reversed.
PAYMENT.CAPTURE.REFUNDEDitem.refundedOne-to-one with refunding the capture id.
PAYMENT.CAPTURE.REVERSEDinvoice.updated, plus either item.charged_back or item.refundedThe v2 event has no reason code, so FloPay enriches it through PayPal's Disputes API before selecting exactly one item-event path.
PAYMENT.SALE.REVERSEDinvoice.updated, plus either item.charged_back or item.refunded, except for chargeback_reimbursementFloPay uses the normalized v1 reason_code. A chargeback_reimbursement restores the invoice without any item event.
BILLING.SUBSCRIPTION.CREATEDsubscription.created
BILLING.SUBSCRIPTION.ACTIVATEDsubscription.created or subscription.reactivatedsubscription.created on first activation; subscription.reactivated when a previously cancelled subscription is restored.
BILLING.SUBSCRIPTION.UPDATEDsubscription.updated
BILLING.SUBSCRIPTION.CANCELLEDsubscription.cancelled
BILLING.SUBSCRIPTION.SUSPENDEDsubscription.paused
BILLING.SUBSCRIPTION.EXPIREDsubscription.expired
BILLING.SUBSCRIPTION.PAYMENT.FAILEDinvoice.overduePayPal exhausted its retry window — Flo does not issue further off-session retries.
VAULT.PAYMENT-TOKEN.CREATED(no outbound)Internal: FloPay stores the token on user_payment_method with type='paypal' for upsell reuse.
VAULT.PAYMENT-TOKEN.DELETED(no outbound)Internal: 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, any unrecognised value, or an absent reason falls back to a refund and is surfaced for review. The first classification is kept for that provider reversal identity so a replay cannot produce both a refund and a chargeback.

chargeback_reimbursement is a reinstatement: it 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 PayPal's Disputes API using the original capture id. A matching dispute with a recognized reason and an escalated CHARGEBACK, PRE_ARBITRATION, or ARBITRATION lifecycle stage is treated as positive chargeback evidence.

A missing capture id or PayPal client, an empty or malformed response, a failed API request, or otherwise unclassifiable evidence uses the fallback refund path and is surfaced for review. That classification is then frozen for the provider reversal identity.

subscription.resumed is emitted when a previously suspended subscription returns to active. Depending on the originating PayPal flow this can arrive via BILLING.SUBSCRIPTION.ACTIVATED or BILLING.SUBSCRIPTION.UPDATED — FloPay normalizes on its own state transition, not the inbound PayPal event name.

Recurring Billing Behaviour

PayPal — not Flo — drives the recurring billing cycle for direct PayPal subscriptions. Renewals fire PAYMENT.CAPTURE.COMPLETED against the subscription, and FloPay emits subscription.renewed (with the linked invoice.paid).

Off-session retries are PayPal's responsibility. FloPay does not retry a failed PayPal recurring charge — once BILLING.SUBSCRIPTION.PAYMENT.FAILED arrives, PayPal has already exhausted its dunning window.

  • Events — full outbound event catalog and examples
  • Payloads — outbound field-level schema
  • Overview — outbound webhook contract
  • Direct PayPal — gateway setup and lifecycle

On this page