PayPal webhook
Collect checkout, payment, refund, dispute, subscription and invoice events from PayPal.
Research Preview. This connector is rolling out account by account. Ask your Intempt contact to enable it for your workspace.
The PayPal webhook collects payment activity: checkouts approved, captures completed and declined, refunds, disputes opened and resolved, subscriptions through their lifecycle, invoices paid.
For a merchant taking PayPal this is the revenue spine, and disputes in particular are a signal that rarely reaches the customer profile.
Available events
| Event | Description |
|---|---|
CHECKOUT.ORDER.APPROVED | Buyer approved the order. Fires for every approved order |
CHECKOUT.ORDER.COMPLETED | Order completed. Marketplace and platform integrations only |
CHECKOUT.ORDER.PROCESSED | Order processed (Orders v2, marketplaces) |
PAYMENT.ORDER.CREATED | Order created (Payments v1, deprecated; the v1 order carries no payer) |
PAYMENT.ORDER.CANCELLED | Order cancelled (Payments v1, deprecated) |
PAYMENT.AUTHORIZATION.CREATED | Payment authorized, not yet captured |
PAYMENT.AUTHORIZATION.VOIDED | Authorization voided |
PAYMENT.CAPTURE.COMPLETED | Capture succeeded. This is the payment |
PAYMENT.CAPTURE.DECLINED | Capture declined |
PAYMENT.CAPTURE.DENIED | Capture denied |
PAYMENT.CAPTURE.REFUNDED | Capture refunded |
PAYMENT.REFUND.FAILED | Refund failed. The buyer has not been refunded |
PAYMENT.SALE.COMPLETED | Sale completed (Payments v1). Still fires for every Subscriptions v1 recurring charge |
PAYMENT.SALE.DENIED | Sale denied (Payments v1) |
PAYMENT.SALE.REFUNDED | Sale refunded (Payments v1) |
CUSTOMER.DISPUTE.CREATED | Dispute opened. dispute_life_cycle_stage tells you whether it's an INQUIRY or money already reversed |
CUSTOMER.DISPUTE.RESOLVED | Dispute closed, with outcome |
BILLING.SUBSCRIPTION.CREATED | Subscription created, before buyer approval |
BILLING.SUBSCRIPTION.ACTIVATED | Subscription active. Also fires on resume from SUSPENDED |
BILLING.SUBSCRIPTION.UPDATED | Plan, quantity or shipping revised. Full post-change subscription, no diff |
BILLING.SUBSCRIPTION.CANCELLED | Subscription cancelled |
BILLING.SUBSCRIPTION.SUSPENDED | Paused by the merchant, or automatically after repeated payment failures |
BILLING.SUBSCRIPTION.EXPIRED | Finite plan reached its last cycle |
BILLING.SUBSCRIPTION.PAYMENT.FAILED | Recurring charge failed. Failure detail is in billing_info.last_failed_payment |
INVOICING.INVOICE.CREATED | Invoice created as DRAFT. The recipient hasn't been notified yet |
INVOICING.INVOICE.PAID | Invoice paid. Also fires on PARTIALLY_PAID and PAYMENT_PENDING, so check status before counting revenue |
INVOICING.INVOICE.CANCELLED | Invoice cancelled |
INVOICING.INVOICE.REFUNDED | Invoice refunded |
VAULT.PAYMENT-TOKEN.CREATED | Payment method saved |
VAULT.PAYMENT-TOKEN.DELETED | Payment method removed |
CATALOG.PRODUCT.CREATED | Catalog product created |
CATALOG.PRODUCT.UPDATED | Catalog product changed. Full product, no diff |
Each event is normalised onto a named stream, so you build segments and journey triggers on the event name rather than on the payload shape.
Two subscription notes:
- Don't also subscribe to
RISK.DISPUTE.CREATED. It's deprecated and superseded byCUSTOMER.DISPUTE.CREATED. Enabling both double-counts every dispute. - There's no recurring-payment-succeeded event. A successful subscription charge arrives as an ordinary
PAYMENT.SALE.COMPLETEDcarryingbilling_agreement_id. Intempt uses that field to emitbilling_successand separate it from a one-off sale.
Identity
Checkout, subscription and invoice events carry the buyer inline: payer.email_address, subscriber.email_address or the invoice recipient's billing_info.email_address.
Captures, refunds and disputes carry no buyer. The payee on those payloads is you, the merchant. Intempt never maps payee.email_address or payee.merchant_id to a person. To find the buyer it follows the payload's links back to the checkout order and reads payer.email_address from there. A dispute payload has buyer.name, which is a display string, not an identity.
Two consequences:
- A refund or dispute on a Payments v1 sale can't be resolved to a buyer once PayPal switches v1 off, because the lookup goes through the deprecated v1 payment resource.
- A refund or dispute on an invoice payment resolves through the invoice recipient instead, using
invoice_number.
payer.payer_id is stable per PayPal account per merchant, but it's PayPal's id, not yours. Intempt matches on email first. customer.merchant_customer_id on vault events is your own customer id and the strongest join key when you supply it at vaulting.
Endpoint
The collector path for PayPal events is:
https://<your collector host>/webhooks/events/paypalPayPal doesn't let you add request headers, so the source token travels in the endpoint URL Intempt gives you.
Signature verification is a call back to PayPal, not a local HMAC. PayPal sends PAYPAL-TRANSMISSION-ID, PAYPAL-TRANSMISSION-SIG, PAYPAL-CERT-URL and related headers, and the only supported way to check them is POST /v1/notifications/verify-webhook-signature with the webhook id and the raw body. Intempt makes that call for every delivery and rejects anything PayPal doesn't confirm. The envelope id is the idempotency key, so a redelivery never double-counts.
Historical load
PayPal's historical load runs on the certified Airbyte source source-paypal-transaction
(Airbyte name Paypal Transaction). It loads Transaction Search, Invoicing and Disputes records
using the same client_id and client_secret you enter above, so one set of credentials covers
both halves.
One caveat worth knowing: Transaction Search only returns transactions from the last 3 years, and PayPal serves it in 31-day windows, so a long backfill runs as many requests.
Setup
PayPal webhooks are configured per app in the Developer Dashboard. Create the source in Intempt first so you have the endpoint.
Configure PayPal
-
Create a PayPal source on the Integrations page and copy the endpoint URL.
-
In the PayPal Developer Dashboard, open Apps & Credentials, choose Live, and open the app whose events you want.
-
Under Webhooks, click Add Webhook and paste the Intempt endpoint.
-
Select the event types in the table above. Leave
RISK.DISPUTE.CREATEDunticked. -
Save and copy the Webhook ID into the Intempt source. Intempt needs it for signature verification.
-
Use Webhook simulator in the dashboard, or make a small live capture, and confirm
PAYMENT.CAPTURE.COMPLETEDappears in the Intempt event stream.
