PayPal webhooks: Simulator tests, event names and signature checks

To test PayPal webhooks, send a Webhooks Simulator or sandbox event to a free webhooks.sh bin (no signup, 48 hours, 100 requests a day) and read the PAYPAL-TRANSMISSION headers and JSON live. PayPal only posts to HTTPS on port 443 and retries failures up to 25 times over 3 days.

PayPal posts a JSON event and five PAYPAL-* signature headers to an HTTPS URL on port 443, and the quickest way to see a real payload is to point the Webhooks Simulator at a free bin. This page covers Simulator versus sandbox events, the headers, both ways to verify signatures, retries and event names.

Send a Simulator event to a free bin

  1. Create a free bin in your browser at view.webhooks.page or run curl -X POST https://webhooks.sh/v1/bins to get an ingest_url and a view_url.
  2. Log in to the PayPal developer dashboard and open the Webhooks Simulator.
  3. Paste your bin's ingest URL (any path works, like https://<bin>.webhooks.page/paypal), pick an event type such as PAYMENT.CAPTURE.COMPLETED, and click Send Test.
  4. Watch the request show up live in the bin viewer with its headers and JSON body, which often takes less than a minute.

You can also fire the same mock event from a script by calling POST /v1/notifications/simulate-event with a sandbox access token.

# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins

# 2. Or ask PayPal's API to post a mock event to it
API=https://api-m.sandbox.paypal.com/v1/notifications
curl -X POST "$API/simulate-event" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://<bin>.webhooks.page/paypal",
       "event_type": "PAYMENT.CAPTURE.COMPLETED"}'

Simulator events vs sandbox app events

Mock events from the PayPal webhook simulator are fine when you just want to inspect the payload shape and write your event_type routing, but they aren't tied to an app, don't show up in your dashboard's webhook events list, can't be resent, and can't be verified with the postback endpoint.

To test signature verification and resends, open Application management in the developer dashboard and subscribe your bin's URL to your sandbox REST app (an app takes up to 10 webhook URLs, each subscribed to chosen event types or * for all events), then run a sandbox payment so PayPal sends a real app event.

BehaviorSimulator mock eventSandbox app event
Tied to a REST appNoYes
Listed in the dashboard's webhook eventsNoYes
Can be resentNoYes, from the dashboard or the resend API
Postback verificationNot supportedWorks with your webhook ID
Webhook ID for self-verificationThe literal string WEBHOOK_IDYour webhook's ID

Headers PayPal sends and where they go

Every delivery carries five PAYPAL-* headers, and each one maps straight to a field in the verify-webhook-signature request, while webhook_id is the only field you pull from your own app settings instead of the request. None of the five header names contains authorization, cookie, key, token or secret, which are the words the viewer redacts, so all five show in plain text in a webhooks.sh bin and you can copy them as they are.

HeaderWhat it holdsverify-webhook-signature field
PAYPAL-TRANSMISSION-IDID of the transmissiontransmission_id
PAYPAL-TRANSMISSION-TIMEDate and time of the transmissiontransmission_time
PAYPAL-TRANSMISSION-SIGBase64 signaturetransmission_sig
PAYPAL-CERT-URLURL of the X.509 signing certificatecert_url
PAYPAL-AUTH-ALGOSigning algorithm, e.g. SHA256withRSAauth_algo

Verify the signature: postback or self-check

You have two ways to verify a PayPal webhook signature, and both of them need the raw request body rather than re-serialized JSON.

For the postback check, send the five header values, your webhook_id (the ID from when you subscribed the listener URL) and the raw event body as webhook_event to POST /v1/notifications/verify-webhook-signature on api-m.sandbox.paypal.com, then check that verification_status comes back as SUCCESS and not FAILURE. For a local self-check, build the pipe-delimited string transmission_id|transmission_time|webhook_id|crc32 where crc32 is the decimal CRC32 of the raw body, grab the X.509 certificate from PAYPAL-CERT-URL (cache it so you aren't fetching it on every hit), and verify the base64 PAYPAL-TRANSMISSION-SIG against that string using the certificate's public key and SHA256.

The postback endpoint doesn't support Simulator events, so to verify a mock event, self-check it with the literal string WEBHOOK_ID as the webhook ID. See our webhook security guide for more on handling raw bodies and signatures.

API=https://api-m.sandbox.paypal.com/v1/notifications
curl -X POST "$API/verify-webhook-signature" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "auth_algo": "<PAYPAL-AUTH-ALGO>",
    "cert_url": "<PAYPAL-CERT-URL>",
    "transmission_id": "<PAYPAL-TRANSMISSION-ID>",
    "transmission_sig": "<PAYPAL-TRANSMISSION-SIG>",
    "transmission_time": "<PAYPAL-TRANSMISSION-TIME>",
    "webhook_id": "<your webhook ID>",
    "webhook_event": <raw event body>
  }'

# Response: {"verification_status": "SUCCESS"} or "FAILURE"

Retries, resends and the numbers to know

PayPal expects a 200 or other 2xx status code from your listener and retries any non-2xx delivery up to 25 times over 3 days before marking it Failed.

A free webhooks.sh bin always replies with HTTP 200, which means PayPal counts every delivery as succeeded and never retries against it, so to watch PayPal retry in action you need your own endpoint that returns an error status. If events aren't showing up at all, PayPal's troubleshooting points to two common culprits: a blocked port 443 on your server, or a listener domain that's been flagged by URL filtering. For more on handling duplicate deliveries and backoff, read the webhook retries guide.

RuleValue
Webhook URLs per REST appUp to 10
ListenerHTTPS on port 443
Counts as deliveredHTTP 200 or any other 2xx
Retries after a non-2xxUp to 25 times over 3 days, then marked Failed
Manual resendWebhook Events dashboard or the resend API
Simulator deliveryOften within a minute

Event names worth subscribing to

  • Payments: PAYMENT.CAPTURE.COMPLETED fires when a payment capture completes, alongside PAYMENT.CAPTURE.DENIED, PAYMENT.CAPTURE.PENDING, PAYMENT.CAPTURE.REFUNDED and PAYMENT.CAPTURE.REVERSED.
  • Orders: CHECKOUT.ORDER.APPROVED and CHECKOUT.ORDER.COMPLETED (the completed event is for marketplaces and platforms).
  • Subscriptions: BILLING.SUBSCRIPTION.CREATED, BILLING.SUBSCRIPTION.ACTIVATED, BILLING.SUBSCRIPTION.CANCELLED, BILLING.SUBSCRIPTION.SUSPENDED and BILLING.SUBSCRIPTION.PAYMENT.FAILED.
  • Disputes: CUSTOMER.DISPUTE.CREATED, CUSTOMER.DISPUTE.UPDATED and CUSTOMER.DISPUTE.RESOLVED.

Subscribe only to the events your handler uses, or to * while exploring in sandbox.

Keep the URL or move to localhost

A free bin lasts 48 hours and accepts 100 requests a day (up to 100KB each), which is plenty for Simulator runs and sandbox checkout tests. Once a bin expires it stops accepting requests, and if that URL is still attached to your sandbox app PayPal's deliveries will start failing and queuing up retries, so either remove the webhook URL from your app when you're done or claim the bin with a free Google sign-in to stop the 48-hour timer and keep a rolling 30 days of history at the same URL.

If you want a persistent named endpoint on webhooks.host with 250,000 requests a month and payloads up to 5MB, Solo is $12 a month and includes the wsh CLI, so you can run wsh listen <endpoint> --to localhost:3000 to relay live PayPal webhooks straight to your local server.

FAQ

How do I test PayPal webhooks without a server?

Point the PayPal Webhooks Simulator at a free webhooks.sh bin URL and the request shows up live in your browser with its headers and JSON body. You don't need to sign up or install anything.

Can I verify PayPal Simulator events with verify-webhook-signature?

No. Mock events from the Simulator aren't tied to an app, and the POST /v1/notifications/verify-webhook-signature postback endpoint doesn't support them. To test verification, either trigger a real event from a sandbox app or self-verify the Simulator payload using the literal string WEBHOOK_ID as the webhook ID.

How many times does PayPal retry a failed webhook?

PayPal retries up to 25 times over 3 days whenever your listener returns a non-2xx response, and marks the event Failed after the last attempt. You can resend failed events from the Webhook Events dashboard or by calling POST /v1/notifications/webhooks-events/{event_id}/resend.

Can PayPal send webhooks to localhost or port 8080?

No, PayPal only posts webhooks to public HTTPS URLs on port 443. Use a public test bin to inspect payloads in the browser, or use the wsh CLI on the Solo plan to relay deliveries from your public endpoint to localhost.

Which PayPal webhook event means a payment completed?

PAYMENT.CAPTURE.COMPLETED is the event that fires when a payment capture completes.

How many webhooks can a PayPal app have?

A PayPal REST app can have up to 10 webhook listener URLs, and each URL can subscribe to its own list of event types or to * for all events.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.