Integrations

Connect RevKlik to your payment gateways, App Store Connect, and Google Search Console, plus sharing, email reports, and team access.

The Integrations view (top-bar tab) has four tabs: Payments, Apps, Search, and Imports.

Payments

RevKlik books revenue from these gateways. Xendit, DOKU, Mayar, and Paywuz serve Indonesian merchants. Polar serves merchants who sell worldwide.

GatewayRegion
XenditIndonesia
DOKUIndonesia
MayarIndonesia
PaywuzIndonesia
PolarGlobal

Webhook endpoints for Midtrans, Stripe, and Lemon Squeezy exist, but their cards are not shown in the dashboard yet, so they are not listed as supported.

Gateway integrations are webhook-driven: copy the RevKlik webhook URL shown on the gateway’s card and paste it into the provider’s webhook settings, then store the signing key. Revenue lands in your revenue panels automatically. Polar has its own panel, described below. If a gateway has no card in your dashboard yet, report its sales with the server events API.

Server events API

For purchases a webhook cannot reach (App Store notifications, a backend of your own), report them from your server:

curl -X POST https://<your-revklik-host>/api/v1/events/server \
  -H "Authorization: Bearer rv_..." \
  -H "Content-Type: application/json" \
  -d '{
    "site_id": "<site id>",
    "name": "purchase",
    "revenue": 9.99,
    "currency": "USD",
    "external_id": "<transaction id>",
    "source": "app_store",
    "props": { "product": "pro", "country": "ID", "environment": "Production" },
    "occurred_at": "2026-10-24T09:30:00Z"
  }'

Use a site token (rv_) with the events:write scope. revenue is in major units; send a negative number for a refund, under its own key such as <transaction id>:refund. The call is idempotent on external_id: a repeat returns "duplicate": true and books nothing. Pass visitor_id (the _kp_vid value from the browser) or session_id (_kp_sid) to attribute the sale to that visitor’s source and campaign. A revenue of 0 is stored as a plain event, not a conversion.

Rail, product and net revenue

Three optional fields feed the cross-product portfolio view:

FieldMeaning
railPayment rail, lowercase: apple, google_play, polar, doku, pakasir, stripe, lemonsqueezy, midtrans, xendit, mayar, paywuz, manual. Defaults from source (app_store* → apple, google* → google_play, polar* → polar, …), else the source itself.
productProduct slug, up to 80 characters. Defaults to props.product, then props.app.
net_revenueWhat you keep, in the same currency and sign as revenue, never larger in size. Send it only when the source knows the real proceeds (for example Apple’s developer proceeds).

Without net_revenue, RevKlik computes the net when the event is stored: the fee is |revenue| × percent + fixed amount (the fixed amount is converted into the event currency), and net = max(0, |revenue| − fee) with the sign of revenue. A refund (<id>:refund) without net_revenue keeps the net/gross ratio of the original sale. Each event records how its net was obtained (reported, rule or refund_ratio). Nets are stored at ingest and never recomputed when you edit a rule later. Replaying a sale with net_revenue (for example re-running a Polar import) upgrades a stored rule estimate to the reported figure.

Polar orders carry Polar’s own fee (platform_fee_amount); the webhook and the order import store net_amount − platform_fee_amount as the reported net, and fall back to the 4% + 0.40 USD rule when Polar sends no fee. A sale the buyer’s browser already reported on the thank-you page is not booked a second time: the confirmed order (webhook or import) adopts the nearest browser row of the same site, rail and currency within 10 minutes, whatever amount the browser sent (it reports the list price, the order what was paid), and fills in that row’s order id, amount, rail and net. If the order names its buyer (revklik_visitor_id) only that buyer’s row qualifies. A browser row that arrives after its order is stored as duplicate. The same visitor firing the same conversion on the same page again within a minute is stored but excluded as a duplicate. For a rail with confirmed orders, scripts/import-polar-orders.ts also marks browser rows in the covered period that no order claims as unconfirmed.

Built-in fees (editable per rail on the Portfolio page, or GET/PUT /api/fee-rules with a dashboard session): apple 15%, google_play 15%, polar 4% + 0.40 USD, stripe 2.9% + 0.30 USD, doku 0.7%, pakasir 0.7%, every other rail 0%. A stored sale with no net is never reported as net = gross: the portfolio computes its net from your fee rule for its rail when you read it, and reports how many rows were estimated that way (estimated_rows, net_source: "estimated"). bun scripts/backfill-revenue.ts (in apps/app) stores those nets, rails and exclusions on old rows.

Test sales

A sale whose source or rail names a test (tracking_smoketest, test_checkout, …), or whose props._test is true, is stored with excluded_reason = "test" and left out of every dashboard, API and MCP number, like bot traffic. Double-booked sales are excluded the same way with excluded_reason = "duplicate", and browser-reported sales no provider order confirms with "unconfirmed".

Portfolio revenue

curl "https://<your-revklik-host>/api/v1/portfolio/revenue?from=2026-10-01&to=2026-10-31&currency=IDR" \
  -H "Authorization: Bearer rvt_..."

Needs an account token (rvt_) with analytics:read; a site token only sees its own site. It covers every site you own or have accepted a team seat on, excludes bot traffic, and converts each sale at today’s rates into currency (default: your most common site currency). from and to are inclusive UTC days (default: the last 30 days). The data object holds:

  • currency, from, to
  • totals: gross, net, sales (revenue > 0), refunds (revenue < 0), refund_amount, estimated_rows (rows whose net came from the fee rule at read time)
  • by_site, by_product, by_rail, daily: gross, net and sales per group (by_rail also has estimated_rows; a row without a rail takes the rail its payment provider implies, e.g. app_store_sales_report → apple)
  • recent: the 20 newest revenue events with revenue, net_revenue, net_source, currency, converted_gross, converted_net and occurred_at

The same data is available to AI agents as the portfolio_revenue MCP tool.

Polar

Each site has its own Polar endpoint. In the dashboard open Integrations → Payments → Polar and follow the panel:

  1. In Polar open Settings → Webhooks → Add Endpoint, paste the URL the panel shows (https://app.revklik.com/api/webhooks/polar?site_id=<your site id>) and leave Format on Raw.
  2. Tick order.paid and order.refunded. Subscription renewals are orders too, so no subscription events are needed.
  3. Copy the endpoint’s secret into Webhook secret and save. The secret is write-only: RevKlik reports that it is saved, never its value.

Deliveries are verified with Standard Webhooks (both the whsec_ base64 form and Polar’s older UTF-8 secret form), per site: another site’s secret, RevKlik’s own billing secret and unsigned requests are rejected. The panel lists the last deliveries and flags a signature mismatch.

Put revklik_visitor_id and revklik_session_id in the checkout metadata to attribute the sale to the visitor. RevKlik books each order once, using the order’s net amount (after discounts, before tax) and Polar’s own fee. A refund is booked on the day it happened (the order.refunded event time), not on the order’s date. Polar reports a cumulative refunded total, so the first refund is one negative <order id>:refund row and each later refund adds a row for the amount the total rose by (<order id>~<cents already refunded>:refund).

Optionally save an Organization Access Token (Settings → Access Tokens, scopes orders:read and refunds:read). It enables Sync orders now (last 30 days, 12 months or all time, up to 50 new orders per run) and an hourly check of the last 14 days that catches missed webhooks, refunds and Polar’s real fee. With refunds:read each refund is dated by Polar’s own refund record; without it the order’s last-modified time stands in.

Bot filtering

Traffic that is not a person is stored but flagged, and excluded from every dashboard, API and MCP number. It is flagged when the browser reports automation (navigator.webdriver), when Cloudflare Bot Management scores the request as a bot, when it comes from a datacenter-only city, or when a session behaves in ways a person cannot: several scroll depths in the same millisecond within a few seconds of loading with no click, bursts of pageviews, or more than 100 clicks. An hourly job re-judges recent sessions, so a rule change reclassifies old data too.

Session duration counts each gap between two events up to 30 minutes, so a tab left open overnight no longer inflates the average. Traffic sources are merged by name: bing, bing.com and www.bing.com are one source, and only pageviews from outside your own domain count as a source.

Apps

App Store Connect syncs App Store downloads, proceeds, and price changes into the same view as your web revenue, so a mobile app and its website share one dashboard. Create an App Store Connect API key (Users and Access → Integrations), then enter the Issuer ID, Key ID, the .p8 private key, the app’s Apple ID, and your vendor number. Apple shows the private key once, so keep the file. The same sales can also arrive through the server events API when you prefer to report them from your own backend.

Google Search Console imports search performance: authorize with Google, then Sync now to pull clicks, impressions, CTR, and position per query. The panel shows 30-day totals and a per-query table.

Imports

Bring history in without waiting for it to accumulate:

  • Polar: connect the site from Integrations → Payments → Polar (see Polar). With an access token saved, Sync orders now brings in existing orders, their fees and refunds.
  • OneDollarStats: import visitors, pages, countries, and sources with an API key and the site domain as registered there.

Share and access

The Share tab bundles everything public:

  • Public dashboard: a read-only link to your analytics; disable it any time.

  • Live widget: an embeddable live-visitor bubble or badge:

    <script
    	async
    	src="https://app.revklik.com/klikapa-live.js"
    	data-widget-token="YOUR_TOKEN"
    ></script>
  • API tokens: account- or site-scoped tokens with granular scopes (sites:read, analytics:read, analytics:export, tokens:read, tokens:write). Limits depend on your plan; see billing. The same tokens also work over MCP, so coding agents can read your analytics directly.

Email reports

In Settings → Email reports, schedule a recurring summary to your account email: daily, weekly, or monthly. Reports can be toggled on and off and show when they were last sent.

Team

In Settings → Team, invite teammates by email as Admin, Editor, or Viewer. Invitees need an existing revklik account; owners can change roles and remove members at any time.