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:
| Event Type | Trigger |
|---|
onboarding.started | User begins an onboarding flow |
onboarding.completed | User completes an onboarding flow |
survey.completed | User submits a survey response |
payment.completed | Purchase transaction succeeds |
payment.failed | Purchase transaction fails |
subscription.canceled | User cancels a subscription |
push.delivered | Push notification delivered to device |
push.opened | User taps a push notification |
email.opened | User opens an email |
email.clicked | User clicks a link in an email |
message.clicked | User interacts with an in-app message |
journey.completed | User completes a journey/lifecycle flow |
journey.exited | User exits a journey before completion |
experiment.exposure | User is exposed to an experiment variant |
user.identified | Anonymous user is linked to a known user ID |
webhook.test | Test event sent from the dashboard |
Every webhook delivery sends a JSON payload with the following structure:
{
"id": "evt-550e8400-e29b-41d4-a716-446655440000",
"type": "payment.completed",
"created_at": "2026-02-19T10:00:00Z",
"data": {
"user_id": "user-123",
"product_id": "premium_monthly",
"transaction_id": "txn-456",
"price": 9.99,
"currency": "USD",
"platform": "ios"
}
}
| Field | Type | Description |
|---|
id | string (uuid) | Unique event identifier |
type | string | One of the 16 supported event types |
created_at | string (datetime) | ISO 8601 timestamp of when the event occurred |
data | object | Event-specific payload (varies by event type) |
Every webhook request includes the following headers:
| Header | Description |
|---|
Content-Type | application/json |
X-AppDNA-Signature | HMAC-SHA256 signature for verification |
X-AppDNA-Event | Event type (e.g., payment.completed) |
X-AppDNA-Delivery-Id | Unique delivery identifier for deduplication |
X-AppDNA-Timestamp | Unix timestamp of when the request was sent |
User-Agent | AppDNA-Webhooks/1.0 |
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 the X-AppDNA-Signature header in the format:
X-AppDNA-Signature: sha256=<hex-encoded-signature>
The signing input is the raw JSON stringified request body.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const providedSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(providedSignature, 'hex')
);
}
// Express.js example
app.post('/webhooks/appdna', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-appdna-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.APPDNA_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log(`Received ${event.type}:`, event.data);
res.status(200).send('OK');
});
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
provided = signature.replace('sha256=', '')
return hmac.compare_digest(expected, provided)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhooks/appdna', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-AppDNA-Signature', '')
payload = request.get_data()
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
print(f"Received {event['type']}: {event['data']}")
return 'OK', 200
Always use constant-time comparison (e.g., crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python) when verifying signatures. Simple string comparison is vulnerable to timing attacks.
Retry Policy
If your endpoint returns a non-2xx response or times out, AppDNA retries the delivery with exponential backoff:
| Retry | Delay |
|---|
| 1st | 30 seconds |
| 2nd | 2 minutes |
| 3rd | 10 minutes |
| 4th | 1 hour |
| 5th | 4 hours |
Maximum retries: 5 attempts total (1 initial + 5 retries = 6 total attempts).
Delivery timeout: 30 seconds. If your server does not respond within 30 seconds, the delivery is marked as failed and scheduled for retry.
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
GET /api/v1/webhooks/endpoints
Returns all configured webhook endpoints for the current application.
Create Endpoint
POST /api/v1/webhooks/endpoints
Request body:
{
"name": "Production Backend",
"url": "https://api.example.com/webhooks/appdna",
"events": ["payment.completed", "payment.failed", "subscription.canceled"],
"description": "Handles payment lifecycle events",
"headers": {
"X-Custom-Header": "custom-value"
}
}
| Field | Type | Required | Description |
|---|
name | string | Yes | Human-readable endpoint name |
url | string | Yes | Destination URL (HTTPS required) |
events | string[] | Yes | Array of event types to subscribe to |
description | string | No | Optional description |
headers | object | No | Custom headers to include in deliveries |
Webhook URLs must use HTTPS. HTTP endpoints are rejected.
Update Endpoint
PATCH /api/v1/webhooks/endpoints/{id}
Update any field on an existing endpoint. Partial updates are supported.
Delete Endpoint
DELETE /api/v1/webhooks/endpoints/{id}
Permanently deletes the endpoint and all associated delivery history.
Test Endpoint
POST /api/v1/webhooks/endpoints/{id}/test
Sends a webhook.test event to the endpoint. Use this to verify your endpoint is reachable and correctly verifying signatures.
Rotate Signing Secret
POST /api/v1/webhooks/endpoints/{id}/rotate-secret
Generates a new signing secret for the endpoint. The previous secret is immediately invalidated.
After rotating a secret, update your server’s verification code with the new secret before the next delivery attempt.
Disable Endpoint
POST /api/v1/webhooks/endpoints/{id}/disable
Temporarily disables the endpoint. No deliveries are attempted while disabled. Events that occur during this period are not queued.
Enable Endpoint
POST /api/v1/webhooks/endpoints/{id}/enable
Re-enables a previously disabled endpoint.
List Deliveries
GET /api/v1/webhooks/endpoints/{id}/deliveries
Returns a paginated list of delivery attempts for the endpoint, including status, response code, and timestamps.
Delivery Detail
GET /api/v1/webhooks/deliveries/{id}
Returns full details for a specific delivery attempt, including request/response headers and body.
Retry Failed Delivery
POST /api/v1/webhooks/deliveries/{id}/retry
Manually retries a failed delivery. The retry is attempted immediately, independent of the automatic retry schedule.
Event Catalog
GET /api/v1/webhooks/events
Returns the full list of supported webhook event types with descriptions.
Your webhook integration is working correctly when you receive a webhook.test event at your endpoint and can successfully verify the signature.