Webhooks


Introduction

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).

Create a Webhook

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 secret is only returned once at creation time. Save it immediately — it cannot be retrieved again. Use it to verify incoming webhook signatures.

Verifying 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.

Webhook Payload

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.

Retry Policy

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.

Regenerate Secret

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..." }

Update / Delete

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.