Appy

Attribution

Ad networks and postbacks

Attribute installs from Apple Ads, Google Ads and Meta campaigns that never touched an Appy link, and send installs and events to ad networks or to your own server. Enterprise plan.

What Appy reads from each network

NetworkPlatformSourceSetup
Apple AdsiOS 14.3+The AdServices attribution token, checked with Apple’s attribution APINone. On by default; turn it off in the app’s Ad networks settings.
Google AdsAndroidA gclid in the Google Play install referrerNone.
Meta (Facebook, Instagram)AndroidThe Meta Install Referrer, or the same encrypted data in the Play install referrerYour Install Referrer Decryption Key in the dashboard, and metaAppId in the Android SDK.

An Appy link always wins: when a link opened the app or a store referrer carried an Appy click, the install belongs to that link. Ad network data comes next, before any probabilistic match. Network installs are verified and appear with link: null and an attribution.network object.

attribution.network
{
  "name": "meta",
  "campaignId": "6100000000003",
  "campaignName": "Spring installs",
  "adGroupId": "6100000000002",
  "adGroupName": "Lookalike TR",
  "adId": "6100000000001",
  "adName": "Story video 15s",
  "touch": "view",
  "sourceApp": "instagram"
}

name is apple_ads, google_ads or meta. Apple Ads adds keywordId, conversionType (Download, Redownload or PreOrder) and country; Google Ads adds clickId (the gclid). For Meta, adGroupId and adGroupName are the ad set and adId and adName the ad. Fields a network does not report are left out. App statistics count these installs in installs.byNetwork.

Apple Ads

On the first launch the iOS SDK reads the attribution token from AdServices and sends it with the launch. Appy asks Apple’s attribution API from its servers. No App Tracking Transparency prompt and no IDFA are involved. Apple’s record can take a few seconds to appear, so Appy retries every 5 seconds up to three times; the result is stored on the install and is not part of onAttribution in the app.

  • Apple returns ids, not names: campaign, ad group, keyword and ad. Look the names up in Apple Ads.
  • Apple only claims installs from Apple Ads; attribution: false leaves the install as it was.
  • Simulators have no AdServices and send no token. Devices in developer mode get Apple’s test payload, which Appy ignores.
  • An Apple Ads result replaces an organic or probabilistic one, never a verified Appy link.

Google Ads app campaigns put a gclid in the Google Play install referrer, which the Android SDK already sends on the first launch. Appy stores it as network.clickId with the referrer’s utm_campaign. Appy does not upload conversions back to Google Ads; keep Firebase or your MMP for that.

Meta

  1. 1

    Copy the decryption key

    In the Meta App Dashboard open App settings > Basic, find the Android section and copy the Install Referrer Decryption Key (64 hexadecimal characters). Business Manager admin access is needed.

  2. 2

    Paste it into Appy

    Open your app in the Appy dashboard and paste it under Ad networks > Meta, or send it as metaDecryptionKey with PATCH /v1/apps/{appId}. Appy stores it encrypted and never returns it; the app shows metaDecryptionKeySet: true.

  3. 3

    Set your Facebook App ID in the SDK

    The Android SDK then queries the Facebook, Instagram and Facebook Lite apps on the first launch. Its manifest already declares the <queries> entries.

Kotlin
Appy.init(this, AppyConfig("appy_pk_...", listOf("acme.appy.to")).apply {
    metaAppId = "1234567890"
})

Meta reports touch: view for installs after an ad impression and click after a tap. Installs Meta does not claim stay as they were.

Postbacks and webhooks

A postback is an HTTPS URL Appy calls when something happens in your app. Use it to report installs to an ad network that accepts click-id postbacks, or with POST as a webhook to your own server. Up to 10 per app, managed in the dashboard or with the API.

TriggerWhen it fires
installOnce per new install, after the attribution is final, including the Apple Ads lookup.
eventFor each new in-app or server event, optionally only for the names in eventNames. Duplicates never fire twice.
Create a postback
curl https://api.appy.to/v1/apps/$APP_ID/postbacks \
  -H "Authorization: Bearer $APPY_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Network installs",
    "trigger": "install",
    "url": "https://postback.network.example/install?click_id={param:clickid}&ts={timestamp}",
    "requiredParameter": "clickid"
  }'

Put the network’s click id macro in the query of your Appy link, for example https://acme.appy.to/spring?clickid={CLICK_ID}. Appy keeps the link’s parameters with the install, so {param:clickid} sends it back. requiredParameter makes the postback fire only for installs whose link carried it. The response includes signingSecret once.

Macros

MacroValue
{install_id} {platform} {os_version} {device_model} {app_version} {locale} {user_id}The install
{attribution_status} {verified} {link_slug} {utm_source} {utm_medium} {utm_campaign}Its attribution
{param:NAME}Any query parameter of the tapped Appy link
{network} {campaign_id} {campaign_name} {ad_group_id} {ad_group_name} {ad_id} {keyword_id}Apple Ads, Google Ads or Meta data
{event_id} {event_name} {revenue} {currency}The event, empty on install postbacks
{timestamp} {timestamp_ms}When the install or event happened, Unix seconds or milliseconds

Values are URL-encoded. Macros cannot appear in the host name, and Appy only calls public HTTPS hosts.

Webhook body

POST body
{
  "type": "event",
  "appId": "0f3c9c2e-1b7d-4c55-9e2f-8a6d4b1c2e3f",
  "occurredAt": "2026-10-15T10:31:04Z",
  "install": {
    "installId": "7f0c2b9e-3a61-4c2e-9d0b-5c1e8f2a9b10",
    "platform": "ios",
    "appVersion": "2.3.1",
    "userId": "u_123",
    "attribution": {
      "status": "attributed",
      "verified": true,
      "link": null,
      "network": {
        "name": "apple_ads",
        "campaignId": "542370539"
      }
    }
  },
  "event": {
    "id": "txn_1000000912345",
    "name": "purchase",
    "revenue": 9.99,
    "currency": "USD",
    "source": "sdk",
    "occurredAt": "2026-10-15T10:31:04Z"
  }
}

Verify the signature

Every request carries Appy-Signature: t=<unix seconds>,v1=<hex>. v1 is HMAC-SHA256 of t, a dot and the raw body (empty for GET), keyed with the postback’s signing secret. Reject requests older than 5 minutes. Appy-Delivery-Id identifies a delivery across retries.

javascript
import crypto from 'node:crypto';

export function verifyAppySignature(header, rawBody, secret) {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  if (parts.v1.length !== expected.length) return false;
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Delivery and retries

  • Any 2xx or 3xx answer counts as delivered. Redirects are not followed.
  • Timeouts (10 seconds), 429 and 5xx are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. Other 4xx answers fail at once.
  • The dashboard and GET /v1/apps/{appId}/postbacks/{postbackId}/deliveries show the last 50 deliveries. Logs are kept for 30 days.
  • POST .../test sends one request with sample data and returns what your endpoint answered.

What this does not cover

Appy is not a certified measurement partner of Meta, Google or TikTok and does not feed their campaign optimization. It does not handle SKAdNetwork or AdAttributionKit conversion values, import ad spend or detect ad fraud. Networks that require a device advertising id (IDFA or GAID) in every postback cannot be served, because the SDKs do not read one.