Skip to main content
Pro Plan15 minutesIntermediate

How do I integrate PayPal for revenue tracking?

Connect Zenovay to your PayPal account to attribute revenue back to traffic sources, campaigns, and individual visitors. Sandbox-first setup with three credentials and webhook signature verification.

paypalrevenueattributionintegrationwebhooks
Last updated:

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.

  1. Sign in to PayPal Developer

    Go to developer.paypal.com and sign in with your PayPal business account. Free; takes about a minute.

  2. Open Apps & Credentials

    In the dashboard, click Apps & Credentials. Toggle to Sandbox for testing, or Live for production.

  3. Create a new app

    Click Create App. Name it something like "Zenovay Analytics". Pick Merchant as the app type. Click Create App.

  4. 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.

  5. 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.

  1. 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_ID
    

    The URL is pre-filled with your website's ID — you don't need to construct it yourself.

  2. 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.

  3. Subscribe to the right events

    Check at minimum:

    • Payment capture completed
    • Payment capture refunded
    • Checkout 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:

  1. 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)
  2. Choose Sandbox or Live

    Match the environment you set up in Step 1. Sandbox uses api-m.sandbox.paypal.com; Live uses api-m.paypal.com. Mixing modes will fail signature verification.

  3. Click Save

    Zenovay validates your credentials by fetching an OAuth2 access token, then verifies the Webhook ID against PayPal. The card flips to Connected.

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:

  1. 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.
  2. From your application or the PayPal SDK, create a sandbox order pointing at your sandbox client_id (set purchase_units[0].custom_id = visitorId if you want best attribution — see Step 4 above).
  3. Complete the checkout with the sandbox buyer account from Step 1.
  4. Within ~10 seconds, PayPal sends a real, signed PAYMENT.CAPTURE.COMPLETED webhook 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:

  1. 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).
  2. 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_events rows for this website
  • Delete all PayPal payments rows for this website
  • Clear the paypal_customer_id field 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:

  1. Open https://developer.paypal.com/dashboard/applications/
  2. Click your PayPal REST API app.
  3. Open the Webhooks section.
  4. 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:

  1. Open https://developer.paypal.com/dashboard/applications and click your app.
  2. Make sure you're on the right tab (Sandbox or Live) — they have separate settings.
  3. Scroll to the Features section (under the Client ID / Secret block).
  4. Tick the Webhooks checkbox.
  5. Click Save Changes at the bottom of the page.
  6. 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.

Was this article helpful?