Server-to-Server Webhooks

This guide describes how to add and configure custom server-to-server webhook.

📘

Keys live at the organization level

S2S API keys for webhooks are managed at the organization level — see Organization settings → S2S API Keys.

Server-to-Server (S2S) Webhooks push every Apphud subscription event to your own backend in real time as a POST request. Build your own logic, analytics, billing automation, or CRM sync without polling the Apphud API — your server reacts to trials, renewals, refunds, and other lifecycle events the moment they happen.

📘

Note

Server-to-server webhooks are available on Expert and Enterprise plans.

Webhooks are sent as a POST request to the URL you specify. You can use this data to implement custom logic or analytics on your own server.

How to Add Webhook?

Step 1 - Preparations on your server

Set up an HTTPS endpoint on your server that accepts POST requests with a JSON body. This is the URL Apphud will deliver webhooks to.

(Optional) Decide on a Secret token to validate incoming payloads. If you set one, Apphud will include it in the X-Apphud-Token HTTP header on every request.

Step 2 - Authentication

In Apphud, open Connections → Integrations, find Server-to-Server webhooks (on the All or Data Platforms tabs), and click Add connection. Choose a Source.

Enter the webhook name and specify the Webhook URL. Events will be sent to this URL using POST requests.

Apphud - Server-to-server Webhook URL field

Step 3 - Security configuration

Optionally specify a Secret token that will be sent with each webhook request in the X-Apphud-Token HTTP header. Use this token to validate received payloads on your side.

Apphud - Server-to-Server Webhook Secret token

Step 4 - Environment configuration

Choose whether to send Production, Sandbox, or both via this webhook.

Step 5 - Set up Filters (optional)

Navigate to Filters tab. Set up filters if needed as described on Integrations overview → Filters tab.

Step 6 - Set up Events list

Navigate to Events tab. Enable events you want to receive webhooks for.

Apphud - Webhook events configuration

Step 7 - Test connection (optional)

Save connection and test it. See How to Test Webhook below for end-to-end testing with services like RequestBin.

Step 8 - Enable and save

After successful testing, Enable connection and Save it.

❗️

Important Note

Apphud sends only one request per webhook without retries. Your endpoint must be reliably reachable and idempotent.

Webhook Structure

The POST body for all webhooks follows the same structure:

{
    "app": {},
    "event": {},
    "user": {}
}

App

This dictionary contains information about the app.

FieldTypeDescription
app.uidStringApphud app ID
app.bundle_idStringiOS Bundle ID
app.package_nameStringAndroid Package Name

Event

This dictionary contains information about the event.

FieldTypeDescriptionPossible values
event.idStringApphud event ID
event.created_atDateDate when this event was created by Apphud.
event.nameStringEvent namee.g. "trial_started"
event.propertiesDictionaryEvent properties. See below.
event.receiptDictionaryApphud receipt information related to the event. See below.
event.storeStringPurchase store type

event.properties

FieldTypeDescriptionPossible values
event.properties.product_idStringProduct ID
event.properties.reasonStringDepending on the event: reason of an expiration, refund reason, or product_id for billing_issue_resolved eventsuser_canceled, billing_issue, declined_price_increase, unavailable_product, unknown_error, app_issue, another_reason, product_id
event.properties.currencyStringLocal currency ISO code
event.properties.usd_priceFloatPrice in USD
event.properties.local_priceFloatPrice in local currency
event.properties.usd_taxFloatTax amount in USD
event.properties.local_taxFloatTax amount in local currency
event.properties.usd_proceedsFloatDeveloper proceeds in USD after Apple/Google commission and taxes, if applicable
event.properties.local_proceedsFloatDeveloper proceeds in local currency after Apple/Google commission and taxes, if applicable
event.properties.offer_typeStringIntroductory or promotional offer payment mode (iOS only)pay_up_front, pay_as_you_go, trial
event.properties.offer_idStringPromotional offer ID
event.properties.unitStringThe increment of time a subscription period is specified in (iOS only)day, week, month, year
event.properties.units_countIntegerNumber of units per subscription period (iOS only)
event.properties.family_sharedBooleanWhether the purchase was used via Family Sharing
event.properties.product_bundle_idStringInternal purchase identifier
event.properties.paywall_idStringInternal Apphud paywall ID
event.properties.placement_idStringInternal Apphud placement ID
event.properties.paywall_nameStringPaywall name as configured in Apphud
event.properties.paywall_identifierStringPaywall identifier as configured in the app / SDK

event.receipt

❗️

Important Note

This is an internal Apphud receipt model.

FieldTypeDescription
event.receipt.idStringApphud receipt ID
event.receipt.product_idStringProduct ID
event.receipt.purchased_atDateTransaction date, example: 2022-03-12T09:38:38.081Z
event.receipt.intro_periodBooleanWhether the subscription is in the introductory period
event.receipt.trial_periodBooleanWhether the subscription is in the trial period
event.receipt.transaction_idStringApp Store transaction ID / Android Order ID
event.receipt.original_transaction_idStringApp Store original transaction ID / Android Purchase Token
event.receipt.price_usdFloatPrice in USD
event.receipt.proceeds_usdFloatRevenue in USD after Apple / Google commission deduction
event.receipt.priceFloatPrice in local currency
event.receipt.proceedsFloatRevenue in local currency after Apple / Google commission deduction
event.receipt.currencyStringLocal currency ISO code, example: USD
event.receipt.quantityIntegerAlways 1
event.receipt.apple_shareFloatStore commission. Can be 0.15 or 0.3.

User

This dictionary contains information about the user.

FieldTypeDescription
user.created_atStringUser created date, example: 2022-03-12T09:38:38.081Z
user.user_idStringUser ID
user.total_spentFloatUser total spent in USD
user.payments_countIntegerUser payments count
user.ipStringUser IP address
user.uidStringApphud user ID (browser user id)
user.idfvStringDevice IDFV on iOS, App Set ID on Android
user.country_iso_codeStringUser country code, example: US
user.time_zoneStringUser's current timezone, example: Asia/Jakarta
user.languageStringUser's current language, example: en
user.internal_attributionObjectIf available, captures data from Web-to-App flows, Apphud Flows, and Apple Search Ads Integration. Fields may include: status, provider, ad_network, campaign, ad_set, keyword, custom1 (Click/Impression for ASA).
user.partner_attributionObjectPartner attribution data, if available. May include attribution provider and campaign metadata such as campaign, ad_network, ad_set, keyword, status.
user.variations[]Array of ObjectsList of Experiments (A/B tests) the user took part in. Each object may include experiment uid, name, variation_name, status, created_at, and launched_at.
user.payingBooleanWhether the user is currently paying
user.currency_country_codeStringCountry code used for currency calculations (ISO 3166-1 alpha-2)
user.emailStringReserved User property for email assigned via Apphud SDK
user.nameStringReserved User property for name
user.phoneStringReserved User property for phone
user.ageStringReserved User property for age
user.genderStringReserved User property for gender
user.properties[]ArrayCustom user properties assigned via Apphud SDK
user.subscriptions[]ArrayAll subscriptions of the user. See below.
user.devices[]ArrayAll user devices. See below.

user.subscriptions[]

Each element of this array is a dictionary with the following structure:

FieldTypeDescriptionPossible values
user.subscriptions[].idStringApphud subscription ID
user.subscriptions[].product_idStringProduct ID
user.subscriptions[].groupStringSubscription group name
user.subscriptions[].statusStringCurrent subscription statustrial, intro, promo, regular, refunded, expired, promotional
user.subscriptions[].intro_periodBooleanWhether the subscription is currently in an introductory period
user.subscriptions[].trial_periodBooleanWhether the subscription is currently in a trial period
user.subscriptions[].started_atDateSubscription start date, example: 2022-03-12T09:38:38.081Z
user.subscriptions[].expires_atDateSubscription expiration date, example: 2024-03-12T09:38:38.081Z
user.subscriptions[].cancelled_atDateSubscription cancellation date, if cancelled
user.subscriptions[].autorenew_enabledBooleanWhether auto-renew is enabled
user.subscriptions[].in_retry_billingBooleanWhether the subscription is in retry billing
user.subscriptions[].introductory_activatedBooleanWhether any introductory offer, including a trial, has been used
user.subscriptions[].environmentStringSubscription environmentsandbox, production
user.subscriptions[].storeStringStore where the subscription was purchasedapp_store, play_store, stripe
user.subscriptions[].kindStringPurchase typeautorenewable, nonrenewable
user.subscriptions[].original_transaction_idStringApp Store original_transaction_id / Google Play / Xiaomi Store purchase_token
user.subscriptions[].environmentStringSubscription environmentsandbox, production
user.subscriptions[].storeStringStore where the subscription was purchasedapp_store, play_store, stripe
user.subscriptions[].kindStringPurchase typeautorenewable, nonrenewable
user.subscriptions[].original_transaction_idStringApp Store transaction ID / Google Play / Xiaomi Store purchase tockenoriginal_transaction_id / purchase_token
user.subscriptions[].autorenew_product_idStringProduct ID that will be auto-renewed at the next billing date. Differs from product_id when the user scheduled a deferred plan change (crossgrade/downgrade) taking effect after the current period ends. From the store's pending renewal info; may be null

user.devices[]

Each element of this array is a dictionary with the following structure:

FieldTypeDescriptionPossible values
user.devices[].idStringApphud device ID
user.devices[].device_idStringDevice identifier
user.devices[].device_typeStringDevice model
user.devices[].device_familyStringDevice family
user.devices[].platformStringDevice platformios, android
user.devices[].app_versionStringCurrent app version on the device
user.devices[].sdk_versionStringApphud SDK version used on the device
user.devices[].os_versionStringOperating system version
user.devices[].idfaString / NullIDFA, if available
user.devices[].start_app_versionStringApp version installed when the user first started the app on this device
user.devices[].carrierString / NullMobile carrier name, if available
user.devices[].push_tokenString / NullPush notification token, if available
user.devices[].idfvString / NullIDFV, if available
user.devices[].adjust_dataObject / NullAdjust attribution data for this device, if available
user.devices[].appsflyer_dataObject / NullAppsFlyer attribution data for this device, if available

Webhooks Examples

Subscription Renewed

{
  "app": {
    "uid": "a1b2c3d4",
    "bundle_id": "com.example.vpnapp",
    "package_name": "com.example.vpnapp"
  },
  "event": {
    "id": "11111111-2222-3333-4444-555555555555",
    "created_at": "2026-03-03T16:33:05.619Z",
    "properties": {
      "usd_tax": 2.658333333333333,
      "currency": "GBP",
      "local_tax": 1.998333333333333,
      "usd_price": 15.95,
      "paywall_id": "pw_1234abcd",
      "product_id": "com.example.vpnapp.monthly",
      "local_price": 11.99,
      "placement_id": "pl_5678efgh",
      "usd_proceeds": 11.29791666666667,
      "family_shared": false,
      "local_proceeds": 8.492916666666666,
      "product_bundle_id": "pb_90ab12cd",
      "paywall_name": "Main Monthly Paywall",
      "paywall_identifier": "paywall_main_monthly"
    },
    "store": "app_store",
    "name": "subscription_renewed",
    "receipt": {
      "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
      "intro_period": false,
      "price_usd": 15.95,
      "purchased_at": "2026-03-04T00:31:08.000Z",
      "transaction_id": "500000000000001",
      "original_transaction_id": "500000000000000",
      "trial_period": false,
      "price": 11.99,
      "quantity": 1,
      "apple_share": 0.15,
      "proceeds_usd": 11.29791666666667,
      "proceeds": 8.492916666666666,
      "product_id": "com.example.vpnapp.monthly",
      "currency": "GBP"
    }
  },
  "user": {
    "created_at": "2025-04-26T18:48:01.627Z",
    "user_id": "USER-UUID-OBFUSCATED",
    "total_spent": 176.27324951,
    "payments_count": 11,
    "ip": "203.0.113.42",
    "uid": "user_ab12cd34",
    "idfv": "IDFV-OBFUSCATED",
    "country_iso_code": "GB",
    "time_zone": "Europe/London",
    "language": "en",
    "partner_attribution": null,
    "internal_attribution": {
      "ad_set": "brand keywords",
      "status": "Non-Organic",
      "keyword": "vpn app",
      "campaign": "UK_Search_Campaign",
      "provider": "Apple Search Ads Integration",
      "ad_network": "Apple Search Ads"
    },
    "variations": [],
    "paying": true,
    "currency_country_code": "GB",
    "email": null,
    "phone": null,
    "name": null,
    "age": null,
    "gender": null,
    "properties": [],
    "attribution": {
      "search_ads_data": {
        "iad-keyword": "vpn app",
        "iad-adgroup-name": "brand keywords",
        "iad-campaign-name": "UK_Search_Campaign",
        "iad-conversion-type": "Download",
        "iad-country-or-region": "GB"
      },
      "search_ads_raw_data": {
        "ad_id": -1,
        "org_id": 1234567,
        "claim_type": "Click",
        "keyword_id": 1111111111,
        "ad_group_id": 2222222222,
        "attribution": true,
        "campaign_id": 3333333333,
        "conversion_type": "Download",
        "country_or_region": "GB"
      },
      "facebook_data": null,
      "tiktok_data": null,
      "adjust_data": null,
      "apphud_data": null
    },
    "subscriptions": [
      {
        "id": "sub_11111111-2222-3333-4444-555555555555",
        "status": "regular",
        "environment": "production",
        "original_transaction_id": "500000000000000",
        "expires_at": "2026-04-03T23:31:08.000Z",
        "cancelled_at": null,
        "started_at": "2025-04-26T18:48:25.000Z",
        "autorenew_enabled": true,
        "in_retry_billing": false,
        "introductory_activated": true,
        "store": "app_store",
        "kind": "autorenewable",
        "group": "iOS",
        "product_id": "com.example.vpnapp.monthly",
        "intro_period": false,
        "trial_period": false
      }
    ],
    "devices": [
      {
        "id": "dev_11111111-2222-3333-4444-555555555555",
        "device_id": "DEVICE-ID-OBFUSCATED",
        "device_type": "iPhone 15 Pro",
        "device_family": "iPhone",
        "platform": "ios",
        "app_version": "3.3.0",
        "sdk_version": "3.6.2",
        "os_version": "18.5",
        "idfa": null,
        "start_app_version": "3.0.5",
        "carrier": null,
        "push_token": null,
        "idfv": "IDFV-OBFUSCATED",
        "adjust_data": null,
        "appsflyer_data": {
          "af_status": "Organic",
          "af_message": "organic install",
          "appsflyer_id": "af_1745693274391_xxxxxx",
          "is_first_launch": false
        }
      }
    ]
  }
}

How to Test Webhook

You can test webhooks using any service that receives HTTP events, for example, RequestBin.com.

Step 1

Open RequestBin.com and click the Create Request Bin button.

Step 2

Copy the Endpoint URL.

RequestBin - Endpoint URL

Step 3

Create a new Webhook in Apphud using this URL. Make sure you selected the Sandbox environment while configuring the webhook.

Step 4

Make a test purchase on your device in Sandbox environment. Read how to do this here. Whenever the webhook is triggered, you'll see a full report on RequestBin:

RequestBin - Received webhook payload

Did this page help you?