Revenue Connectors · Paddle

Connect Paddle to Admaxxer

Forward Paddle Billing's transaction.completed webhook to Admaxxer so every subscription, one-off purchase, and renewal shows up in your revenue attribution, blended MER, and cohort LTV. Five-minute setup, signed via the Paddle-Signature header (HMAC-SHA256), visitors stitched via custom_data.admx_visitor_id.

Open Revenue Settings Back to all connectors

1. Prerequisites

2. Create a Paddle API key (read-only)

Admaxxer uses a read-only key for transaction lookups during webhook handling. Keep your sensitive secret keys in Paddle.

  1. In the Paddle dashboard, go to Developer Tools › Authentication › + New API key.
  2. Name it Admaxxer Revenue Read.
  3. Permissions: Read transactions, Read subscriptions, Read products. Nothing else.
  4. Copy the pdl_live_apikey_... value and paste it into Admaxxer › Revenue Settings › Paddle.

3. Add the webhook endpoint

Paddle Billing sends signed webhook events to a single Admaxxer URL. Replace <YOUR_WEBSITE_ID> with the UUID shown in Revenue Settings.

https://admaxxer.com/api/pixel/webhooks/paddle/<YOUR_WEBSITE_ID>
  1. In the Paddle dashboard, go to Developer Tools › Notifications › + New destination.
  2. Description: Admaxxer Revenue.
  3. Type: Webhook (URL).
  4. URL: paste your Admaxxer webhook URL.
  5. Subscribed events:
    • transaction.completed
  6. Save. Paddle reveals a signing secret (pdl_ntfsec_...).
  7. Copy the secret and paste it into Admaxxer Revenue Settings › Paddle › Webhook signing secret.

The signing secret is what makes the Paddle-Signature header verifiable. Without it, Admaxxer rejects every inbound webhook with 401 invalid_signature.

4. Stamp the visitor ID on every checkout

Pass customData on the Paddle Checkout call. The pixel exposes window.admx.getVisitorId():

// Browser-side, when opening Paddle Checkout
Paddle.Checkout.open({
  items: [{ priceId: 'pri_01abc...', quantity: 1 }],
  customData: {
    admx_visitor_id: window.admx.getVisitorId() || ''
  },
  customer: { email: 'jane@example.com' }
});

Server-driven Subscriptions API

// Node.js — paddle-node-sdk
const txn = await paddle.transactions.create({
  items: [{ priceId: 'pri_01abc...', quantity: 1 }],
  customData: {
    admx_visitor_id: req.cookies['admx_vid'] || ''
  }
});

The pixel writes the visitor ID to a 1p cookie (admx_vid) on first page view. Read it server-side when building the Paddle transaction; pass it client-side via window.admx.getVisitorId().

5. Verify the connection

  1. In Paddle, go to Developer Tools › Notifications, select your Admaxxer endpoint, and click Send test event.
  2. Choose transaction.completed and send.
  3. In Admaxxer, open Revenue Settings. The "Last 50 webhook deliveries" panel shows the test with status ok.
  4. Run a real test purchase in Paddle's sandbox. Inspect the resulting Transaction in Paddle and confirm custom_data.admx_visitor_id is non-empty.
  5. Within 3 seconds, the order appears in the Admaxxer attribution dashboard, joined to the visitor's acquisition source.

6. Troubleshooting

Webhook returns 401 invalid_signature
Signing secret mismatch. Re-copy the pdl_ntfsec_... value from Paddle Notifications into Admaxxer Revenue Settings. Don't paste your API key — only the notification signing secret validates inbound payloads.
Order appears as unattributed
custom_data.admx_visitor_id was empty on the Transaction. Confirm the pixel fires before Paddle.Checkout.open() runs. Inspect the Transaction in the Paddle dashboard and check the custom_data field.
Subscription renewals not appearing
Renewals fire transaction.completed, not a separate event. Confirm your endpoint is subscribed to transaction.completed for both new transactions and renewals (it's a single subscription that covers both).
Refunds not subtracting from MER
Add transaction.updated (with status=refunded) to the same endpoint. Admaxxer uses Paddle's status field on the transaction object to detect refunds.

Frequently asked

Does Admaxxer work with Paddle Classic or only Paddle Billing?
Paddle Billing is the recommended and best-tested integration. The webhook event names, signing-header format, and custom_data field used for visitor stitching all follow the Paddle Billing API. Paddle Classic users can still wire up a forward-compatible custom webhook, but the official guide is for Paddle Billing.
Why use Paddle as merchant of record?
Paddle handles global tax, VAT, and invoicing — you sell SaaS in 100+ countries without registering for tax in each one. Admaxxer joins Paddle's transaction.completed events to your pixel-side acquisition data so the same MER, cohort LTV, and ad-level LTV that you'd get from Stripe-direct also work for Paddle's MoR billing.
How do I add custom_data on a Paddle Checkout?
When opening Paddle Checkout via Paddle.Checkout.open(), pass customData: { admx_visitor_id: window.admx.getVisitorId() }. The field propagates to the Transaction record, which is what transaction.completed webhooks include. For server-driven Subscriptions API calls, set custom_data on the Transaction the same way.
What happens with subscription renewals?
Each renewal fires its own transaction.completed event with a fresh transaction ID. Admaxxer records each as an independent revenue event but attributes them all back to the visitor who created the original subscription, so cohort LTV correctly reflects the total monthly value.
How does Paddle-Signature verification work?
Paddle signs every webhook with HMAC-SHA256 using your endpoint's secret. The Paddle-Signature header carries a timestamp (ts=...) and signature (h1=...). Admaxxer reconstructs the signed payload (timestamp + : + body), HMACs with the secret, and compares constant-time. Mismatches return 401. The 5-minute timestamp window blocks replay attacks.

Next steps