Receive PayPal Platform Webhook
POST/api/webhooks/paypal
Called by PayPal, not by an integrator. Receives webhook events for every merchant connected with Connect with PayPal through SKU.io's single partner webhook. No bearer token.
This endpoint currently requires session authentication; Personal Access Token scope support is in progress.
Routing: the workspace is found from the PayPal merchant id inside the event (resource.payee.merchant_id, resource.merchant_id, resource.purchase_units[0].payee.merchant_id, a dispute's seller merchant id, or a refund's payer). An event with no merchant id, or a merchant not connected to any workspace, is acknowledged with 200 (plain text) so PayPal stops retrying. If routing is temporarily unavailable the response is 503 and PayPal redelivers later.
The signature is verified against SKU.io's platform webhook id (500 if the platform webhook is not configured on this deployment).
Signature verification: every delivery must carry PayPal's PAYPAL-TRANSMISSION-ID, PAYPAL-TRANSMISSION-TIME, PAYPAL-TRANSMISSION-SIG, PAYPAL-CERT-URL and PAYPAL-AUTH-ALGO headers (missing any → 400). SKU.io posts them, together with the exact raw request body and the expected webhook id, to PayPal's verify-webhook-signature API and only continues when PayPal answers SUCCESS; otherwise 400. The body is never re-serialized before verification.
Processing: a verified event is stored once per PayPal event id (redeliveries are acknowledged without a duplicate) and processed in the background; it then appears under List PayPal Webhook Events. A body without id or event_type returns 400. Successful deliveries return 200 {"received": true}.
Authentication: none (PayPal signature headers, verified with PayPal).
Request
Responses
- 200
- 400
- 401
- 403
- 422
- 429
OK
Response Headers
Bad Request
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Validation failed — the body is a field → messages map (Laravel shape) or the platform envelope with a stable machine-readable code. Fix the payload and resubmit.
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.