Overview
Webhooks allow you to receive real-time HTTP notifications when events occur in your AppDNA application. When a subscribed event fires, AppDNA sends a POST request to your configured endpoint with a JSON payload describing the event.Supported Event Types
AppDNA supports 16 webhook event types:Payload Format
Every webhook delivery sends a JSON payload with the following structure:Payment completed data
A store-driven webhook is recorded in the same database write as the store notification it belongs to, and handed to
webhook delivery once that write has committed: on the App Store before AppDNA’s other follow-up work (the entitlement
sync and the internal billing event), on Google Play after the entitlement sync and before the internal events. It is marked
sent only when its delivery records are stored, so it is not lost when the hand-off or the work after it fails: the
store redelivers the notification and a webhook still pending is handed off then. This is at-least-once: if the hand-off
succeeded but marking it sent did not, the redelivery hands it off again, as a new delivery with a new
id — so your
endpoint can, rarely, see the same store event twice (for payment.completed, the same data.transaction_id), never
none — unless it is still undelivered 24 hours after it was recorded, or every allowed attempt was started and the
last one never finished, in which case it is marked failed (see Retry Policy) and can be retried by
hand.
Purchases that reach AppDNA only through an inbound provider webhook (RevenueCat, Adapty and similar) send no payment.completed.
Request Headers
Every webhook request includes the following headers:Signature Verification
Every webhook request is signed with your endpoint secret using HMAC-SHA256. Always verify the signature before processing the payload. The signature is sent in theX-AppDNA-Signature header in the format:
- Node.js
- Python
Retry Policy
If your endpoint returns a non-2xx response or times out, AppDNA retries the delivery with exponential backoff:
Maximum retries: 5 attempts total (1 initial delivery + 4 retries).
A delivery whose first attempt never started (the AppDNA process that recorded it stopped before sending it) is sent by
the retry schedule once it has waited 5 minutes, and that send is its first attempt; if the process stopped in the
middle of the first send, the retry schedule’s send is its second. The attempts toward the 5 are counted like this: the
first when the delivery is recorded, each retry when it is scheduled after a failed attempt, and a send that the sending
process stopped in the middle of when the retry schedule picks the delivery up again — so such a send still counts, and
no delivery is ever sent more than 5 times: once all 5 have started and the last one never finished, the delivery is
marked failed. One AppDNA process sends a given attempt: it re-confirms that it still holds the delivery just before
sending and records the result only while it still does, so two overlapping retry runs never send the same delivery
twice. A test delivery (sent once, never retried) whose one send recorded no answer is marked failed after 5 minutes.
The delivery log shows the attempt the delivery is on, or waiting for, as
n/5 (a test delivery, sent once and never
retried, as 1/1). One that is still pending or waiting to retry 24 hours after it was recorded or last
retried by hand is marked failed too, instead of being delivered late. Either way the reason is in its delivery log, and
it says which case it was: never sent (Never sent: …), attempted and still waiting to retry (Given up: attempted, and still waiting …),
attempted with the last attempt never finished, so there is no answer to show (Given up: attempted, and the last attempt never finished …),
or out of attempts (Given up: every allowed attempt was started, … or Given up: no attempt is left, …);
retry it by hand to send it (that starts a new 5 attempts and a new 24 hours).
Delivery timeout: 30 seconds, for the request and reading the response body. If your server does not respond within 30 seconds, the delivery is marked as failed and scheduled for retry; a response whose status arrived in time but whose body did not finish is judged by its status, with an empty body in the delivery log.
Maximum response body logged: 10 KB. Response bodies exceeding this limit are truncated in delivery logs.
After 50 consecutive delivery failures, the webhook endpoint is automatically disabled. You can re-enable it from the dashboard or via the API.
Management API
All management endpoints require Customer JWT authentication (Authorization: Bearer header).
List Endpoints
••••••; header names are kept.
Create Endpoint
Update Endpoint
headers replaces the stored set: a header whose value is •••••• (as the API returns it) keeps its stored value, a new value replaces it, and a header left out is removed; •••••• for a header that has no stored value is rejected with 400. A new url is checked as on create (422 WEBHOOK_URL_NOT_PUBLIC when it does not resolve to a public HTTPS address); an update that leaves the URL unchanged is not re-checked.
Delete Endpoint
Test Endpoint
webhook.test event to the endpoint. Use this to verify your endpoint is reachable and correctly verifying signatures.
Rotate Signing Secret
After rotating a secret, update your server’s verification code with the new secret before the next delivery attempt.
Disable Endpoint
Enable Endpoint
List Deliveries
Delivery Detail
••••••; AppDNA’s own headers (Content-Type, X-AppDNA-Event, X-AppDNA-Delivery-Id, X-AppDNA-Timestamp, User-Agent) are shown as sent.
Retry Failed Delivery
Event Catalog
Your webhook integration is working correctly when you receive a
webhook.test event at your endpoint and can successfully verify the signature.
