Use outbound webhooks when your backend needs to react after AppActor has verified and normalized a purchase event. This is different from Apple App Store Server Notifications and Google RTDN. Store notifications flow into AppActor. AppActor outbound webhooks flow from AppActor to your server after AppActor has processed the event.

When to use webhooks

Use webhooks for backend work such as:
  • granting or revoking server-side credits
  • syncing subscription state into your own user table
  • sending lifecycle emails or CRM events
  • unlocking backend-only premium resources
  • reconciling refunds, billing issues, grace periods, and renewals
Do not use webhooks as the only in-app access check. Mobile apps should still read CustomerInfo from the SDK, and backend routes should call AppActor with a secret key when they need synchronous access decisions. Sanitized outbound webhook delivery evidence

Setup path

  1. Create an HTTPS endpoint on your backend.
  2. Configure the app-level webhook URL and secret for the AppActor app.
  3. Verify the X-AppActor-Signature header before trusting the payload.
  4. Store eventId or X-AppActor-Delivery to make processing idempotent.
  5. Return a 2xx response only after your handler has durably accepted the event.
  6. Test with sandbox purchases, renewals, cancellations, refunds, billing retry, and replayed deliveries.
App-level signed webhooks receive processed lifecycle events for the app. Event-type filtering belongs to integration connections; app-level signed webhooks are the signed backend callback path documented on this page.
Webhook URLs must use a public DNS hostname over HTTPS on standard port 443. AppActor rejects userinfo, IP literals, localhost or metadata hosts, private or reserved DNS answers, DNS failures, and private-network targets to avoid SSRF and DNS-rebinding risks.

Payload shape

Every delivery uses this envelope:
Webhook payload
The data object contains the normalized store event plus AppActor processing fields, and it depends on the event type. Treat the top-level eventType, eventId, and createdAt fields as the stable delivery envelope, then version your own handler around the fields you consume inside data.

Headers

Verify signatures

AppActor signs timestamp + "." + rawRequestBody with HMAC-SHA256 and your webhook secret.
Node.js
Verify against the raw request body, not a parsed-and-reencoded JSON object. Reformatting JSON changes the bytes and invalidates the signature.

Retries and idempotency

AppActor delivery jobs run up to 5 attempts. Failed attempts are retried with this backoff schedule: The retry policy also has a 2-hour fallback delay if the delivery attempt limit is extended later. A delivery is considered successful when your endpoint returns a 2xx response. Network errors, timeouts, private-network resolution, and non-2xx responses are treated as failures. Make your handler idempotent:
  • store eventId for lifecycle idempotency
  • store X-AppActor-Delivery for delivery-attempt debugging
  • do not grant credits twice if the same eventId is delivered again
  • return 2xx after your job is safely queued if downstream work is asynchronous
  • return non-2xx only when you want AppActor to retry

Delivery troubleshooting

Common event types

The AppActor event name is the normalized lifecycle type. Store-specific Apple notification types and Google RTDN notification numbers are visible in raw event details, but your webhook handler should branch on the normalized AppActor event type.

Test before launch

Before production, verify:
  • Apple test notification is healthy for iOS apps.
  • Google RTDN test notification is healthy for Android apps.
  • A sandbox purchase creates a customer, transaction, and active entitlement.
  • Your webhook endpoint receives subscription.purchased or purchase.completed.
  • Your handler verifies X-AppActor-Signature.
  • Duplicate delivery of the same eventId does not duplicate side effects.
  • Refund, cancellation, renewal, billing retry, and expiration paths are handled.
If your backend needs an immediate yes/no access decision, use Server-side Access Checks. Use webhooks for async lifecycle reactions.