S2S Transaction Validator
Setting Up the S2S Transaction Validator
Use Server-to-Server (S2S) validation when your backend (or a third-party service other than RevenueCat) already validates in-app purchases with Apple App Store and Google Play. In this mode, Magify trusts the purchase data delivered from your server and skips its own store-side validation.
Step 1: Request S2S Access in Magify
Server-to-Server access is provisioned only through Magify technical support. Contact support to request S2S access for your application; they will provide the S2S endpoint URL and the Authorization Code required to authenticate your server requests.
Step 2: Configure Your Server
On your server, after a purchase has been validated with Apple App Store or Google Play, build the payload described below and deliver it to Magify. The full request — method, URL, and required headers — is covered in the Sending the payload subsection after the example.
Request fields
Identity
client_id(string, required) — Magify client identifier. By default it is generated by the SDK and exposed throughMagifyService.Instance.ClientId. If your application assigns its ownclient_idand passes it toMagifyManager, you must send exactly the same identifier in the S2S payload — otherwise the purchase will be attributed to a different user.app_version(string, required) — application version at the time of purchase.country(string, required) — two-letter device/user country code.
Transaction
product_id(string, required) — store product identifier.transaction_id(string, required) — store transaction identifier. Normalized to a lowercase UUID server-side.original_transaction_id(string, required) — original store transaction identifier. For renewals/restores this matches the first transaction in the chain. Normalized to a lowercase UUID server-side.purchased_at(number, required) — purchase timestamp as Unix epoch seconds. Fractional values are accepted.store_name(string, required) — store identifier. For purchases made through the native stores (Apple App Store, Google Play), sendNATIVE. For purchases made through any other store or payment provider, contact Magify technical support to request a dedicatedstore_namecode for your store and use it in the payload.storefront(string, optional) — store region code reported by the native store.environment(string, optional, defaultPRODUCTION) — one of:PRODUCTIONSANDBOX
Money
price(decimal, required) — transaction amount. Must be≥ 0; up to 8 decimal places.currency(string, optional) — ISO 4217 currency code (e.g.USD). Defaults toUSDif omitted.commission_amount(decimal, optional, default0) — store/processor commission deducted from the price. Must be≥ 0; up to 8 decimal places.commission_currency(string, optional) — ISO 4217 currency code for the commission.
Classification
type(string, optional) — purchase type. One of:TEST— test transaction (never counted as revenue);INITIAL_PURCHASE— first purchase of a subscription;NON_RENEWING_PURCHASE— one-off / consumable / non-renewing purchase;RENEWAL— subscription renewal;CANCELLATION— subscription cancellation / refund.
period_type(string, optional) — subscription period type. One of:TRIALNORMAL
is_trial_conversion(boolean, optional, defaultfalse) —trueif this transaction converts a trial into a paid subscription.product_id_type(string, optional) — product kind. One of:consumable_product_idnon_consumable_product_idsubscription_product_idcross_promo_product_iddeeplink_product_idbonus_product_idinfo_product_idrewarded_product_idexternal_product_id
Example payload
{
"app_version": "1.0",
"client_id": "00000000-0000-0000-0000-000000000001",
"country": "US",
"product_id": "com.example.coins_pack_small",
"transaction_id": "2000000812345678",
"original_transaction_id": "2000000812345678",
"purchased_at": 1747310000.22,
"price": 9.99,
"currency": "USD",
"storefront": "US",
"store_name": "NATIVE",
"type": "INITIAL_PURCHASE",
"period_type": "NORMAL",
"is_trial_conversion": false,
"product_id_type": "consumable_product_id",
"environment": "PRODUCTION"
}
Sending the payload
Send the JSON payload above as the body of an HTTP POST request to the S2S endpoint URL for your application. The URL has the following shape:
https://webhook.magify.com/v1/s2s/{app_slug}
{app_slug} is the slug name of your application in Magify — a short, URL-safe identifier assigned to the app (for example, my-game), not a UUID. The same URL is used for both sandbox and production traffic — the environment is distinguished only by the environment field inside the payload. The URL and the bearer token are issued by Magify technical support in Step 1.
The request must include:
- Method:
POST - URL:
https://webhook.magify.com/v1/s2s/{app_slug} Content-Type: application/jsonAuthorization: Bearer <token>— use the bearer token issued by Magify technical support. Requests without this header, or with an invalid token, are rejected.
Example curl:
curl -X POST "https://webhook.magify.com/v1/s2s/{app_slug}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {bearer_token}" \
-d '{ "app_version": "1.0", "client_id": "00000000-0000-0000-0000-000000000001", ... }'
Use "environment": "SANDBOX" in the payload while testing and "environment": "PRODUCTION" for live traffic. The URL stays the same — Magify routes the request based on the environment field.
Step 3: Update the Client SDK
Although the validated purchase reaches Magify over S2S, the client SDK still has to emit analytics events (purchase started, purchase finished, subscription events). To complete the S2S setup you must set SkipVerification to true on every PurchaseInfo you create. In this case the Magify SDK will not request validation through its own services and will rely entirely on the payload your server sends.
How to set the flag
PurchaseInfo is typically created in your purchase callback — most commonly inside the method that implements Unity IAP's IStoreListener.ProcessPurchase, or inside the Magify store wrappers IMinimalInAppStore / IInAppStore. Pass the flag through the constructor:
PurchaseProcessingResult IStoreListener.ProcessPurchase(PurchaseEventArgs e)
{
var product = e.purchasedProduct;
// skipVerification: true — Magify will NOT call its validation service.
// Your server is responsible for sending the validated purchase via S2S.
var purchaseInfo = new PurchaseInfo(
product,
LoadSubscriptionInfo(product.definition.id),
skipVerification: true);
if (purchaseInfo.SubscriptionInfo != null)
OnSubscriptionPurchaseFinished?.Invoke(product.definition.id, purchaseInfo);
OnPurchaseFinished?.Invoke(product.definition.id, purchaseInfo);
return PurchaseProcessingResult.Complete;
}
If only some products use S2S (for example, external shop only), set the flag only for those products and leave the default for everything else.
Aligning client events with the S2S payload
Whatever identifiers reach Magify through the S2S call must match what the client sends for analytics, regardless of whether SkipVerification is set:
client_id— if your app assigns its own client identifier toMagifyManager, send exactly that value in the S2S payload. Otherwise read the SDK default viaMagifyService.Instance.ClientIdand forward it to your backend.transaction_id/original_transaction_id— use the identifiers emitted by the native store (AppletransactionIdentifier/ GoogleorderId) and forward the same values server-side.product_id— match the store product identifier used on the client.
Mismatches will split a purchase across users or duplicate it in reporting.
Hybrid setup
If only some of your purchases are validated externally (for example, a proprietary web shop in addition to Apple/Google), you can keep Magify validation for native flows and use the trusted-purchase path for external ones by filling a TrustedPurchaseRecord on PurchaseInfo. See External Validation for the full client-side patterns (trusted purchases, server-to-server, and mixed setups).
Step 4: Send a Test Transaction
- From your server, send a test purchase payload to the S2S endpoint with
"environment": "SANDBOX"using a sandbox transaction. - In Magify, open Transaction Validator → your application and confirm the test transaction appears in the validation log.
- Once verified, change
"environment"to"PRODUCTION"in the payload for live traffic — the URL and bearer token stay the same.
Notes
- Magify does not re-validate S2S purchases with the native stores — ensure your server performs full Apple/Google receipt validation before forwarding the purchase.
- Duplicate purchases are de-duplicated by
transaction_id. Re-sending the same transaction is safe. - If you later migrate to RevenueCat, switch the integration via Transaction Validator → RevenueCat and stop sending S2S payloads for the affected application.