Skip to content

IAP Payment Webhooks

Updated: Apr 28, 2026

IAP Payment Webhooks provide a server-to-server notification system for your Instant Game. When a player completes an in-app purchase or a refund is processed, Facebook sends an HTTPS POST request to your server with details about the transaction.

Webhooks are the most reliable way to confirm that a purchase was successful and to detect refunds. They complement the client-side SDK flow by providing a server-side signal that is not affected by network interruptions, app crashes, or the player closing the game mid-purchase.

Prerequisites

Before setting up IAP webhooks, ensure you have:

  • An Instant Game app with In-App Purchases enabled.
  • A publicly accessible HTTPS endpoint on your server that can receive both GET and POST requests.
  • Admin access to your app in the App Dashboard (required to view the App Secret).

Subscribing to Webhooks

You subscribe to IAP webhooks through the App Dashboard. Graph API subscription is not supported for IAP webhooks.

Step 1: Navigate to Webhooks

  • Open the App Dashboard and select your app.
  • Navigate to Use Cases in the left sidebar.
  • Click Webhook in the list of use cases.

Step 2: Configure Your Callback URL

  • Enter your Webhook Callback URL — the public HTTPS endpoint where Facebook will send purchase and refund notifications.
  • Enter a Verify Token — an arbitrary string that you define. Facebook sends this token during the subscription verification step so your server can confirm the request originated from your configuration.

Step 3: Find Your App Secret

Your App Secret is used to validate the authenticity of webhook payloads. To find it:

  • In the App Dashboard, select your app and go to Settings > Basic.
  • Your App Secret is displayed on this page. You must have an admin role on the app to view the App Secret.

Keep your App Secret confidential. Never expose it in client-side code or public repositories.

Testing Your Webhook

Before saving your webhook subscription, you should test the callback settings:

  • Click the Test button in the webhook configuration panel. This sends a verification GET request to your endpoint containing the hub.mode, hub.challenge, and hub.verify_token parameters.
  • Your server must echo back the hub.challenge value in the response body. If your server responds correctly, the test passes.
  • After saving the webhook URL successfully, you can also send a test payload to your URL using the test button in the App Dashboard to verify that your server correctly receives and processes the POST payload.

If the test fails, verify that your endpoint is publicly accessible, uses HTTPS, and correctly handles the verification handshake described below.

Your Callback Server

Your callback server must handle two types of requests from Facebook.

Subscription Verification

When you add or modify a webhook subscription, Facebook makes a single HTTPS GET request to your callback URL with the following query string parameters:

ParameterDescription
hub.modeThe string "subscribe".
hub.challengeA random string generated by Facebook.
hub.verify_tokenThe verify token you provided when creating the subscription.

Your endpoint should:

  • Verify the hub.verify_token matches the token you configured. This confirms the request is related to your subscription.
  • Echo back the hub.challenge value in the response body. This confirms to Facebook that your server is ready to receive callbacks.

Example (Node.js):

app.get('/webhook', (req, res) => {  
  const mode = req.query['hub.mode'];  
  const token = req.query['hub.verify_token'];  
  const challenge = req.query['hub.challenge'];  
  
  if (mode === 'subscribe' && token === YOUR_VERIFY_TOKEN) {  
    console.log('Webhook verified successfully.');  
    res.status(200).send(challenge);  
  } else {  
    console.error('Webhook verification failed.');  
    res.sendStatus(403);  
  }  
});

Note for PHP developers: In PHP, dots and spaces in query parameter names are converted to underscores automatically. Access these parameters using $_GET['hub_mode'], $_GET['hub_challenge'], and $_GET['hub_verify_token']. See the PHP manual⁠ for details.

Receiving Updates

After a successful subscription, Facebook sends an HTTPS POST request to your endpoint whenever a purchase succeeds or a refund is processed. Your server must respond with HTTP status code 200.

Important: Any HTTP response other than 200 is treated as an error. Facebook will retry sending the webhook — immediately at first, then with decreasing frequency over the next 24 hours. If your server does not respond correctly, you may receive the same update multiple times. Design your handler to be idempotent.

The request has a content type of application/json and the body contains a JSON-encoded payload with the transaction details.

Note for PHP developers: In PHP, to read the request body:

$data = file_get_contents("php://input");
$json = json_decode($data);

The hub.mode, hub.challenge, and hub.verify_token parameters are not sent with update POST requests — they are only used during the initial subscription verification.

With every POST request, Facebook sends an X-Hub-Signature-256 HTTP header containing the SHA256 signature of the request payload, using your App Secret as the key, prefixed with sha256=. You should validate this signature to verify the integrity and origin of the payload. See Validating Payload Signatures below.

V2 Webhook Payload

Every webhook notification contains a JSON payload with the following fields:

FieldTypeDescription
developer_payloadstringThe developer-provided payload string that was passed to purchaseAsync when the purchase was initiated. Use this for your own tracking purposes (e.g., order references, session IDs).
payment_action_typestring (enum)The type of payment event. See PaymentStatusWebhookEventType for possible values.
user_idint64The Facebook user ID of the player who made the purchase.
product_idstringThe product identifier as defined in your product catalog (e.g., "com.game.gems_100").
purchase_platformstring (enum)The platform where the purchase was processed. See DigitalContentPlatform for possible values.
purchase_price_currencystringThe ISO 4217 currency code of the purchase price (e.g., "USD", "EUR").
purchase_price_amountint64The purchase price amount in the smallest currency unit (e.g., 999 represents $9.99 USD in cents).
purchase_tokenint64A unique token identifying this purchase. Use this token when calling the Consume Purchase API.
envstring (enum)The payment environment. See PaymentEnv for possible values.
versionstringThe payload version identifier. Always "V2" for this payload format.

Enum Reference

PaymentStatusWebhookEventType

Used by the payment_action_type field to indicate the type of payment event.

ValueDescription
PURCHASE_SUCCESSThe purchase was completed successfully. The player has been charged.
REFUND_SUCCESSA refund has been processed for this purchase.

DigitalContentPlatform

Used by the purchase_platform field to indicate where the purchase was processed.

ValueDescription
FBFacebook web payments
GOOGLEGoogle Play billing
APPLEApple App Store billing
BILLINGMeta billing
OCOculus / Meta Quest
EXTERNAL_PROVIDER_STRIPEStripe external payment provider
UNKNOWNUnknown platform

PaymentEnv

Used by the env field to indicate the payment environment.

ValueDescription
PRODProduction environment — real payments.
DEVDevelopment / sandbox environment.
DEV_EXTERNALExternal development environment.
TESTTest environment — no real charges are made.

Example Webhook Payloads

Purchase Success

{  
  "entry": [  
    {  
      "id": "7362434450471825",  
      "time": 1777339377,  
      "changes": [  
        {  
          "developer_payload": "{\"hello\":\"world\"}",  
          "payment_action_type": "PURCHASE_SUCCESS",  
          "user_id": 12345,  
          "product_id": "test_product_001",  
          "purchase_platform": "FB",  
          "purchase_price_currency": "USD",  
          "purchase_price_amount": 999,  
          "purchase_token": 999999999,  
          "env": "DEV",  
          "version": "V2",  
          "field": "in_app_purchase"  
        }  
      ]  
    }  
  ],  
  "object": "application"  
}

Refund

{  
  "entry": [  
    {  
      "id": "7362434450471825",  
      "time": 1777339400,  
      "changes": [  
        {  
          "developer_payload": "{\"hello\":\"world\"}",  
          "payment_action_type": "REFUND_SUCCESS",  
          "user_id": 12345,  
          "product_id": "test_product_001",  
          "purchase_platform": "FB",  
          "purchase_price_currency": "USD",  
          "purchase_price_amount": 999,  
          "purchase_token": 999999999,  
          "env": "DEV",  
          "version": "V2",  
          "field": "in_app_purchase"  
        }  
      ]  
    }  
  ],  
  "object": "application"  
}

Handling Purchase Success

When your server receives a webhook with payment_action_type set to PURCHASE_SUCCESS, you must:

  • Grant the purchased item to the player — Deliver the virtual goods, currency, or content associated with the product_id if you have not already done so from the client-side SDK flow.
  • Call the Consume Purchase API — After granting the item, call FBInstant.payments.consumePurchaseAsync with the purchase_token from the webhook payload to mark the purchase as consumed. This is required for consumable products so the player can purchase the item again.

Critical: If your game fails to call the Consume Purchase API — either from within the FBInstant SDK during the in-game purchase flow or after receiving the IAP webhook notification for a successful purchase — an automatic refund will be triggered within 12 hours. Always call consume purchase immediately after granting the item to the player to prevent unintended refunds.

Example server-side handler (Node.js):

app.post('/webhook', async (req, res) => {  
  // Validate the X-Hub-Signature-256 header first (see Security section)  
  
  const payload = req.body;  
  
  if (payload.payment_action_type === 'PURCHASE_SUCCESS') {  
    // 1. Grant the purchased item to the user  
    // 2. Call consume purchase API with payload.purchase_token  
  }  
  
  res.sendStatus(200);  
});

Handling Refunds

When your server receives a webhook with payment_action_type set to REFUND_SUCCESS, it means a refund has been processed for a previous purchase.

What triggers a refund?

Refunds can be initiated by:

  • The player requesting a refund through Facebook, Google Play, or Apple App Store.
  • An online payment dispute filed by the player.
  • An automatic refund triggered when the Consume Purchase API was not called within 12 hours of a successful purchase (see above).

Historically, refunds account for less than 4% of all transactions.

  • Use refund notifications for internal analytics and tracking. Record which players received refunds, for which products, and the frequency of refunds to monitor your game's purchase health.
  • In-game handling of refunds is at your discretion. You may choose to revoke the purchased item, adjust the player's balance, or take no in-game action — the decision depends on your game's economy and policies.

Validating Payload Signatures

Every webhook POST request includes an X-Hub-Signature-256 header that you should use to verify the request came from Facebook and was not tampered with.

The header value has the format sha256=<hex_signature>, where the signature is an HMAC-SHA256 hash of the raw request body using your App Secret as the key.

Example (Node.js):

const crypto = require('crypto');  
  
function verifyWebhookSignature(req, appSecret) {  
  const signature = req.headers['x-hub-signature-256'];  
  if (!signature) {  
    throw new Error('Missing X-Hub-Signature-256 header');  
  }  
  
  const expectedSignature = 'sha256=' + crypto  
    .createHmac('sha256', appSecret)  
    .update(req.rawBody)  
    .digest('hex');  
  
  if (!crypto.timingSafeEqual(  
    Buffer.from(signature),  
    Buffer.from(expectedSignature)  
  )) {  
    throw new Error('Invalid webhook signature');  
  }  
}

Important: Always use a constant-time comparison function (like crypto.timingSafeEqual) to prevent timing attacks when comparing signatures.

Next Steps

  • In-App Purchases — Complete guide to setting up and integrating IAP in your Instant Game.
  • SDK Reference — Full API documentation for FBInstant.payments methods including consumePurchaseAsync.

Unofficial mirror for reference/search purposes. All content originates from developers.facebook.com — see the source link at the top of each page. Machine-readable indexes: llms.txt · llms-full.txt · About