Webhooks deliver real-time POST notifications to your server when an install is attributed to a DynaLink. Each delivery is signed with HMAC-SHA256 so you can verify the request originated from Dynalink.
All webhook management endpoints require the x-project-key header and auth:sanctum (admin token).
POST https://dynalink.app/api/webhooks
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | HTTPS endpoint that will receive event deliveries |
events |
array | Yes | Event types to subscribe to (see below) |
is_active |
boolean | No | Enable or disable delivery (default true) |
Supported events
| Event | Fired when |
|---|---|
install_attributed |
A device fingerprint is confirmed as a matched install |
curl -X POST https://dynalink.app/api/webhooks \
-H "Content-Type: application/json" \
-H "x-project-key: YOUR_PROJECT_KEY" \
-d '{
"url": "https://yourserver.com/dynalink/webhook",
"events": ["install_attributed"],
"is_active": true
}'
Response
{
"id": 1,
"url": "https://yourserver.com/dynalink/webhook",
"events": ["install_attributed"],
"is_active": true,
"secret": "aBcDeFgH1234...",
"created_at": "2026-05-21T10:00:00.000000Z"
}
{warning} The
secretis only returned once at creation time. Save it immediately — it cannot be retrieved again. Use it to verify incoming webhook signatures.
Every delivery includes an X-Dynalink-Signature header containing the HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
// Node.js example
const crypto = require('crypto')
function verifyDynalinkWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
)
}
// PHP example
function verifyDynalinkWebhook(string $rawBody, string $signature, string $secret): bool {
$expected = hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signature);
}
Always use a constant-time comparison (timingSafeEqual / hash_equals) to prevent timing attacks.
The request body is JSON and matches the attribution API response shape.
{
"matched": true,
"dyna_code": "SUMR99",
"gclid": "Cj0KCQjw...",
"gbraid": null,
"fbclid": "FB.AbCdEfGh...",
"fbclid_captured_at": 1716211200,
"ttclid": null,
"twclid": null,
"li_fat_id": null,
"campaign_id": 4,
"campaign_name": "Summer 2026",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "summer_2026",
"utm_term": null,
"utm_content": null,
"attributed_at": "2026-05-21T10:05:00Z"
}
See the Attribution reference for a full field description.
If your endpoint returns a non-2xx status or times out, Dynalink retries the delivery up to 3 times with exponential backoff:
| Attempt | Delay after failure |
|---|---|
| 1st retry | 60 seconds |
| 2nd retry | 4 minutes |
| 3rd retry | 16 minutes |
Respond with HTTP 2xx as quickly as possible. Offload any heavy processing (database writes, third-party API calls) to a background job.
GET https://dynalink.app/api/webhooks/{id}/regenerate-secret
Returns a new secret. The previous secret is immediately invalidated — update your verification logic before calling this endpoint.
{ "secret": "NewS3cr3tV4lu3..." }
PUT https://dynalink.app/api/webhooks/{id}
DELETE https://dynalink.app/api/webhooks/{id}
PUT accepts the same body parameters as the create endpoint. DELETE returns HTTP 200 on success.