Connecting PayPal lets Zenovay show revenue alongside your traffic data — which marketing source produced the paying customer, which page they converted on, which campaign closed the deal.
You'll need three things from your PayPal Developer account: a Client ID, a Client Secret, and a Webhook ID. You create the webhook in your PayPal dashboard (Zenovay shows you the exact URL to paste) and copy its ID back into Zenovay. We recommend setting up Sandbox first to verify everything works, then switching to Live.
⚠️ Important — testing tip you should read first
PayPal's Webhooks Simulator (the "Send Test" button in PayPal Developer Dashboard) sends mock events that cannot be signature-verified by design. This is documented PayPal behavior — the simulator binds signatures to a placeholder
WEBHOOK_ID, not your real webhook ID, so the verification API always returns FAILURE for simulator events.To verify your integration actually works, do a real Sandbox checkout (Step 5 below) — that produces a properly signed event Zenovay can verify.
Step 1: Create a PayPal REST API app — and enable the Webhooks feature
⚠️ Enable the Webhooks feature on your app.
New PayPal REST API apps default to "Accept payments only". Your app needs the Webhooks feature ticked so it can create and verify webhooks. This is a 30-second one-time setup.
Sign in to PayPal Developer
Go to developer.paypal.com and sign in with your PayPal business account. Free; takes about a minute.
Open Apps & Credentials
In the dashboard, click Apps & Credentials. Toggle to Sandbox for testing, or Live for production.
Create a new app
Click Create App. Name it something like "Zenovay Analytics". Pick Merchant as the app type. Click Create App.
Copy your Client ID and Secret
PayPal shows your Client ID (long alphanumeric string) and Secret (click Show to reveal). Keep this tab open — you'll paste these into Zenovay in Step 3.
Tick the Webhooks feature checkbox
Still on your app's settings page, scroll down to the Features section (sometimes labelled App settings). You'll see checkboxes like Accept payments, Vault, Payouts, Subscriptions, Log in with PayPal, and Webhooks.
Tick the Webhooks checkbox, then click Save Changes at the bottom of the page.
No need to regenerate your Client Secret — the new permission applies to fresh OAuth tokens immediately.
Step 2: Create the webhook in PayPal and copy its Webhook ID
Open your website's settings in Zenovay and go to the Revenue tab (under the Analytics section), then click the PayPal card. The form shows a copyable webhook URL that already includes your website ID — copy it, then register the webhook on PayPal's side.
Copy your Zenovay webhook URL
In the PayPal card in Zenovay, copy the webhook URL shown at the top of the form. It looks like:
https://api.zenovay.com/api/webhooks/paypal/YOUR_WEBSITE_IDThe URL is pre-filled with your website's ID — you don't need to construct it yourself.
Add the webhook in PayPal
Back in PayPal Developer, inside your app's settings, scroll to Sandbox Webhooks (or Live Webhooks). Click Add Webhook and paste the URL you copied.
Subscribe to the right events
Check at minimum:
Payment capture completedPayment capture refundedCheckout order approved(optional; logged for observability)
Click Save. PayPal generates a Webhook ID — keep it handy for Step 3.
Step 3: Connect in Zenovay
Still on the PayPal card in your website's Revenue settings tab:
Paste your three credentials
- Client ID — from Step 1
- Client Secret (this is the API Key field) — from Step 1
- Webhook ID — the ID PayPal generated in Step 2 (required)
Choose Sandbox or Live
Match the environment you set up in Step 1. Sandbox uses
api-m.sandbox.paypal.com; Live usesapi-m.paypal.com. Mixing modes will fail signature verification.Click Save
Zenovay validates your credentials by fetching an OAuth2 access token, then verifies the Webhook ID against PayPal. The card flips to Connected.
Step 4: Tag your orders with the visitor ID (recommended)
For payment-to-traffic-source attribution to work most accurately, set the visitor's anonymous Zenovay ID as custom_id when creating the PayPal order on your server:
// Node.js example using @paypal/paypal-server-sdk
const visitorId = req.cookies['zv_visitor_id']; // or however your client passes it
await paypalClient.ordersCreate({
body: {
intent: 'CAPTURE',
purchase_units: [{
amount: { currency_code: 'USD', value: '42.00' },
custom_id: visitorId, // ← Zenovay visitor UUID goes here
}],
},
});
When the capture completes, Zenovay reads purchase_units[0].custom_id and joins the payment to the visitor's session — including the source, campaign, and pages they visited before paying.
If you don't set custom_id, attribution still works via the payer's email address, but it's less precise (only matches if the visitor previously identified themselves with the same email).
How attribution works
Zenovay attributes each payment to a traffic source and lets you switch between several attribution models from the Revenue tab:
- Last Touch (default) — 100% of the credit goes to the last channel before the conversion. Good for measuring what closes deals.
- First Touch — 100% of the credit goes to the channel that first brought the visitor in. Good for measuring what drives discovery.
- Linear — splits credit evenly across every channel the visitor used.
- Position-Based — 40% to the first channel, 40% to the last, the remaining 20% across everything in between.
- Time-Decay — more credit to channels closer to the conversion, on a 7-day half-life.
You pick the model in the Revenue tab; the breakdown recalculates for the model you choose.
Step 5: Sandbox testing — use a real checkout, not the Simulator
Don't use PayPal's Webhooks Simulator to test. Simulator events deliberately can't be signature-verified (PayPal documents this) — they will always fail Zenovay's verification check. Use a real Sandbox checkout instead, as described below.
The right way to verify your Sandbox integration:
- PayPal Developer Dashboard → Sandbox → Accounts. You should see a default Personal (buyer) sandbox account with a fake email + password — these are the test buyer credentials.
- From your application or the PayPal SDK, create a sandbox order pointing at your sandbox client_id (set
purchase_units[0].custom_id = visitorIdif you want best attribution — see Step 4 above). - Complete the checkout with the sandbox buyer account from Step 1.
- Within ~10 seconds, PayPal sends a real, signed
PAYMENT.CAPTURE.COMPLETEDwebhook to Zenovay. Confirm it landed by opening the Revenue tab on your website's dashboard — the capture should appear with the buyer's sandbox email + amount.
If the capture doesn't appear after ~30 seconds:
- Check the Verification status badge on the PayPal card in your Revenue settings — it shows the most recent error.
- Confirm Sandbox vs Live mode matches: a Sandbox webhook fired against a Live integration (or vice versa) will fail signature verification.
- Confirm the webhook is still registered in PayPal: PayPal Dashboard → your app → Webhooks. If it was deleted, re-add it and paste the new Webhook ID back into Zenovay.
Switching from Sandbox to Live
When you're ready for production:
- In PayPal Developer, create a Live REST API app and a Live webhook (repeat Step 1 + Step 2 on the Live tab — the Live webhook gets its own Webhook ID).
- In Zenovay, open the PayPal card, paste the Live credentials and Live Webhook ID, switch the toggle to Live, click Save.
PayPal sends events to whichever environment matches the registered Webhook ID, so keep the credentials and Webhook ID for the same environment together.
Disconnecting
Open your website's settings → Revenue tab → click the PayPal card → Remove.
By default, disconnecting just removes your PayPal credentials. Your existing payment records, attribution history, and Revenue dashboard data stay intact (subject to your plan's data retention window). You can reconnect at any time and continue from where you left off.
Optional: also delete historical PayPal data
The disconnect dialog includes a checkbox: "Also permanently delete N PayPal records ($X total)". Tick it only if you want a clean slate — for example, decommissioning a test integration or doing privacy housekeeping.
When checked, Zenovay will:
- Delete all PayPal
payment_eventsrows for this website - Delete all PayPal
paymentsrows for this website - Clear the
paypal_customer_idfield on every identified user (the user record itself is preserved — they keep their Stripe ID, etc.)
The deletion runs in a single transaction — if any step fails, all three are rolled back, so you never end up in a half-deleted state. The action is irreversible.
Don't forget to remove the PayPal-side webhook
Disconnecting in Zenovay only stops Zenovay from accepting events. PayPal will keep firing the webhook at our endpoint until you remove it in your PayPal Developer Dashboard:
- Open https://developer.paypal.com/dashboard/applications/
- Click your PayPal REST API app.
- Open the Webhooks section.
- Find the webhook pointing at
https://api.zenovay.com/api/webhooks/paypal/{your-website-id}and Remove.
Until you remove it on PayPal's side, our endpoint responds 410 Gone to each delivery. PayPal abandons retries after 410, so this is mostly cosmetic — but it's good hygiene and prevents the webhook from cluttering PayPal's own retry logs.
Limitations
- One PayPal account per Zenovay website. If you accept payments through multiple PayPal accounts, configure each on its own Zenovay website.
- Connect is manual-credential only. There's no "Log in with PayPal" OAuth-redirect connect — you paste the Client ID, Client Secret, and Webhook ID directly.
Troubleshooting
"Verification failed" on a real test webhook
Most likely a Sandbox/Live mismatch — the webhook fired by sandbox.paypal.com against credentials marked Live (or vice versa) will fail signature verification. Check the toggle on the PayPal card matches the environment of your credentials.
If you used PayPal's Webhooks Simulator, that's a known limitation — simulator events cannot be signature-verified by design (PayPal binds the signature to a placeholder WEBHOOK_ID literal, not your real webhook ID). Always test with a real Sandbox checkout instead.
"NOT_AUTHORIZED" / 403 error
This usually means the Webhooks feature isn't enabled on your PayPal REST API app, or you're using a Personal sandbox account where webhooks require a Business sandbox account.
Fix:
- Open https://developer.paypal.com/dashboard/applications and click your app.
- Make sure you're on the right tab (Sandbox or Live) — they have separate settings.
- Scroll to the Features section (under the Client ID / Secret block).
- Tick the Webhooks checkbox.
- Click Save Changes at the bottom of the page.
- If you're on Sandbox, confirm you're using a Business sandbox app, not a Personal one.
You don't need to regenerate the Client Secret. The new permission applies to fresh OAuth tokens immediately.
Reached the 10-webhook limit on your PayPal app
PayPal caps each REST API app at 10 webhooks. If you've registered Zenovay across many Zenovay websites you may hit this. Delete unused webhooks at https://developer.paypal.com/dashboard/applications, then add a fresh webhook and paste its ID into the relevant Zenovay website.