Skip to main content

Webhooks

AppDNA sends HTTP POST requests to your server when events occur in your app. Webhooks let you sync data to your backend, trigger workflows, update CRMs, feed events into data warehouses, or power any custom integration.

Overview

When you create a webhook endpoint in the AppDNA dashboard, you choose which event types to subscribe to. When a matching event occurs, AppDNA delivers a signed JSON payload to your endpoint URL via HTTP POST. Every delivery includes an HMAC-SHA256 signature so you can verify the payload originated from AppDNA and was not tampered with.

Setup

1

Navigate to Webhooks

Go to Console > Settings > Webhooks in the AppDNA dashboard.
2

Add an endpoint

Click Create Endpoint and enter your HTTPS URL. Non-HTTPS URLs are not supported in production.
3

Select event types

Choose which event types this endpoint should receive. You can select individual events or subscribe to all events.
4

Save and copy the signing secret

After saving, the dashboard displays your signing secret. Copy it immediately — it is only shown once. You will use this secret to verify webhook signatures.
The signing secret is only displayed once when you create the endpoint. If you lose it, you must rotate the secret from the endpoint settings, which invalidates the previous secret.

Event Types

AppDNA supports 16 webhook event types organized into four categories:

Billing

EventDescription
subscription.createdA new subscription was created
subscription.renewedAn existing subscription renewed successfully
subscription.cancelledA subscription was cancelled (may still be active until period end)
subscription.expiredA subscription period ended without renewal
trial.startedA free trial began
trial.convertedA trial converted to a paid subscription
trial.expiredA trial ended without converting
refund.processedA refund was processed for a transaction

Onboarding

EventDescription
onboarding.completedA user completed an onboarding flow
onboarding.skippedA user skipped or dismissed an onboarding flow

Engagement

EventDescription
survey.completedA user submitted a survey response
experiment.exposureA user was exposed to an experiment variant
push.deliveredA push notification was delivered to a device
push.openedA user opened a push notification

System

EventDescription
paywall.impressionA paywall was displayed to a user
config.updatedA config bundle was regenerated (new version published)

Payload Structure

Every webhook payload follows a consistent structure:
{
  "id": "evt_01HYX9K3M7N8P2Q4R5S6T7V8W9",
  "type": "subscription.created",
  "timestamp": "2026-02-19T10:30:00Z",
  "data": {
    "user_id": "user-123",
    "subscription_id": "sub-456",
    "plan": "premium_monthly",
    "amount": 9.99,
    "currency": "USD"
  }
}
FieldTypeDescription
idstringUnique event ID. Use this for idempotency.
typestringThe event type (e.g., subscription.created).
timestampstringISO 8601 timestamp of when the event occurred.
dataobjectEvent-specific payload. Contents vary by event type.
The data object contents vary by event type. Refer to the specific event type documentation in the dashboard for the full schema of each event.

Signature Verification

Every webhook request includes an x-appdna-signature header containing an HMAC-SHA256 signature of the raw request body. Always verify this signature before processing the payload. The signature is computed as:
HMAC-SHA256(signing_secret, raw_request_body)
The header value is hex-encoded and prefixed with sha256=:
x-appdna-signature: sha256=a1b2c3d4e5f6...

Verification Examples

const crypto = require('crypto');

function verifyWebhook(req, signingSecret) {
  const signature = req.headers['x-appdna-signature'];
  if (!signature) return false;

  const expected = 'sha256=' + crypto
    .createHmac('sha256', signingSecret)
    .update(req.rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express middleware example
app.post('/webhooks/appdna', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifyWebhook(req, process.env.APPDNA_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  console.log(`Received ${event.type}: ${event.id}`);

  // Process the event asynchronously
  processEvent(event).catch(console.error);

  // Respond quickly with 200
  res.status(200).send('OK');
});
Always use a constant-time comparison function (e.g., crypto.timingSafeEqual, hmac.compare_digest, Rack::Utils.secure_compare) to prevent timing attacks.

Retry Policy

If your endpoint returns a non-2xx status code or does not respond within 15 seconds, AppDNA retries the delivery with exponential backoff:
AttemptDelay After Failure
130 seconds
22 minutes
310 minutes
41 hour
54 hours
After 5 failed attempts for a single event, that delivery is marked as failed. You can view failed deliveries and manually retry them from the dashboard. After 50 consecutive failures across any events, the webhook endpoint is automatically disabled and you receive an email notification. You can re-enable the endpoint from the dashboard after fixing the issue.

Best Practices

  1. Respond with 2xx quickly. Return a 200 OK as soon as you receive the payload. Process the event asynchronously in a background job or queue. If your endpoint takes too long to respond, the delivery will be marked as failed and retried.
  2. Verify signatures on every request. Never process a webhook payload without verifying the x-appdna-signature header. This protects you from forged requests.
  3. Handle idempotency. Webhook deliveries can be retried, which means your endpoint may receive the same event more than once. Use the id field to deduplicate events. Store processed event IDs and skip duplicates.
  4. Use HTTPS. AppDNA only delivers webhooks to HTTPS endpoints in production. During development, you can use a tunneling tool like ngrok to expose a local endpoint.
  5. Monitor delivery health. Check the webhook delivery logs in the dashboard periodically. If you see a pattern of failures, investigate your endpoint’s availability and response times.
To test webhooks locally during development, use a tunneling tool like ngrok and point your webhook endpoint to the tunnel URL. Sandbox webhooks (adn_test_ environment) are fully functional and isolated from production.