Partner integrations
How partner connections are made, verified and monitored — OAuth, manual credentials, live verification, deliveries, test events and failure handling.
Updated 2026-09-02
WhichClick connects to 70+ partners (self-attributing networks, ad networks, DSPs, CRMs, analytics tools). Every connection is treated as untrusted until it is verified against the partner and stays monitored afterwards.
Connecting#
| Method | Partners | How |
|---|---|---|
| OAuth (Connect with …) | Google Ads, Meta, TikTok, Snapchat, Pinterest, Reddit, LinkedIn | One click from Partners → {partner}. Tokens are stored encrypted, the account id is auto-discovered when the token can see exactly one account, and the connection is verified before it is enabled. Requires the partner app credentials on the server (OAUTH_* variables, see Self-hosting). |
| Manual credentials | All | Paste the fields from the partner dashboard. Saving runs the same verification. |
| REST / MCP | — | Integrations are managed in the dashboard only; API keys cannot create partner connections. |
Verification#
Saving, the OAuth callback, Verify now and a daily scheduler job all run the same check:
| Level | What happens | Partners |
|---|---|---|
api | A real read call with the stored credentials (customer / advertiser / account lookup, or an ingestion call in validate mode). Returns the account label shown in the UI. | Google Ads, Meta, TikTok, Snap, Apple Search Ads, Pinterest, Reddit, LinkedIn, Klaviyo, Braze, Iterable, OneSignal, Mixpanel, Amplitude, Segment, GA4, CleverTap, AppLovin, Unity Ads, ironSource, Mintegral |
test_message | A message is posted to the channel. | Slack |
reachability | Postback URL templates are rendered with sample values, the host must resolve and answer over TLS. Credentials themselves cannot be checked. | Ad networks, DSPs, affiliate platforms, custom webhook |
none | Partner needs no credentials (attribution comes from the SDK). | Huawei AppGallery, Xiaomi GetApps |
A failed verification stores the integration disabled with status = error and the exact reason. There is no partial or assumed state.
Deliveries#
Every outbound call is a row in the delivery log (dashboard: Integrations → Recent deliveries, per partner under Deliveries):
| Kind | Trigger |
|---|---|
install, event | Install / in-app event forwarded (SAN Conversions APIs, native analytics payloads, or postback templates) |
test | Send test event — a purchase of 1.00 using the partner's test mode where one exists (Meta / TikTok test_event_code, Snap validate endpoint) |
verify | Verification result |
cost | Cost sync pull with the number of campaign-day rows |
protect_sync | Google Ads negative-IP exclusions and campaign pauses |
Success clears lastError; a failure increments the failure counter and after five consecutive failures the integration flips to status = error (visible on the Integrations page and in alerts). Skips (event disabled by mapping, install not attributed to the partner, missing consent for Google's app conversion API) are not deliveries and are not counted.
Event forwarding formats#
| Partner | Format |
|---|---|
| Meta | Conversions API for App Events (action_source=app, anon_id, extinfo) |
| TikTok | Events API v1.3 (event_source=app, ttclid) |
| Snapchat | Conversions API v3 (action_source=MOBILE_APP, hashed ids) |
| Google Ads | App Conversion Tracking API (per-app link id; needs the raw advertising id under consent) |
| Amplitude, Mixpanel, Segment, GA4 (Measurement Protocol), Klaviyo, Braze, Iterable, CleverTap, MoEngage, Airship, OneSignal, Mailchimp | Native HTTP APIs with the documented payloads |
| Everything else | Documented postback URL template with {macros} (GET or POST) |
Event names can be renamed or disabled per partner (eventMapping), and forwarding can be limited to installs attributed to that partner (onlyAttributed).
Cost sync#
Automatic daily spend requires the cost capability, which is only offered where a real fetcher exists: Google Ads (GAQL), Meta (Insights), TikTok (Reporting), Apple Search Ads (Campaign Management API), AppLovin, Unity Ads, ironSource and Mintegral. Currencies always come from the partner API; a row without a currency aborts the run rather than defaulting. Other partners import spend via CSV or POST /api/v1/costs. See Cost import & sync.
Google Ads: one connection#
The google_ads integration is the single credential store for GAQL cost sync, Protect's negative-IP exclusions and incident auto-pause. Connect with Google (OAuth) or paste an OAuth client + refresh token; the developer token can be supplied per organization or platform-wide (GOOGLE_ADS_DEVELOPER_TOKEN). The App Conversion Tracking link id remains per app under Apps & SDK.
Security#
- Credentials are AES-256-GCM encrypted with
CREDENTIALS_KEY; only the prefix of a secret is ever displayed. - OAuth state is HMAC-signed with the organization and user id and expires after 15 minutes.
- Short-lived tokens (Snap, Pinterest, Reddit) are refreshed automatically before use and the new tokens are persisted.
- Every connect, verify, test, enable/disable and disconnect is written to the audit log.