Webhooks

Receive real-time HTTP POST notifications when deeplink events occur in your project.

Overview

Webhooks allow your server to be notified automatically when events happen in your Grivn project. When an event occurs, Grivn sends an HTTP POST request to your configured URL with a JSON payload describing the event. You can use webhooks to trigger workflows, sync data, or build integrations.

Event Types

Subscribe to one or more of the following event types when creating a webhook:

EventDescriptionDelivery
deeplink.clickA deeplink was clickedBatched (30s window)
deeplink.installApp install confirmed via SDKImmediate
deeplink.createA new deeplink was createdImmediate
deeplink.updateA deeplink was modifiedImmediate
deeplink.deleteA deeplink was deletedImmediate

Payload Format

All webhook payloads follow this structure:

{
  "event_type": "deeplink.create",
  "timestamp": "2026-03-24T12:00:00Z",
  "project_id": "proj_abc123",
  "data": {
    "id": "dl_xyz789",
    "name": "My Campaign Link",
    "short_link": "https://example.com/abc",
    "link": "https://app.example.com/promo"
  }
}

Click Event Payload

Click events are batched in a 30-second window and delivered as an aggregated payload:

{
  "event_type": "deeplink.click",
  "timestamp": "2026-03-24T12:00:30Z",
  "project_id": "proj_abc123",
  "data": {
    "deeplink_id": "dl_xyz789",
    "window_start": "2026-03-24T12:00:00Z",
    "window_end": "2026-03-24T12:00:30Z",
    "total_clicks": 3,
    "clicks": [
      {
        "deeplink_id": "dl_xyz789",
        "ip_address": "203.0.113.1",
        "device_type": "mobile",
        "clicked_at": "2026-03-24T12:00:05Z"
      }
    ]
  }
}

Signature Verification

Every webhook request includes two headers for signature verification:

  • X-Grivn-Signature — HMAC-SHA256 signature in the format sha256=<hex>
  • X-Grivn-Timestamp — Unix timestamp (seconds) when the request was sent

The signature is computed over the string <timestamp>.<request_body> using your webhook secret as the HMAC key. Always verify signatures to ensure the request is authentic and has not been tampered with.

Node.js

const crypto = require('crypto');

function verifyWebhook(body, signature, timestamp, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + body)
    .digest('hex');
  return signature === expected;
}

// Express middleware example
app.post('/webhook', (req, res) => {
  const body = req.body; // raw body string
  const signature = req.headers['x-grivn-signature'];
  const timestamp = req.headers['x-grivn-timestamp'];

  if (!verifyWebhook(body, signature, timestamp, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(body);
  console.log('Event:', event.event_type);
  res.status(200).send('OK');
});

Python

import hmac
import hashlib

def verify_webhook(body: str, signature: str, timestamp: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        f'{timestamp}.{body}'.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
    body = request.get_data(as_text=True)
    signature = request.headers.get('X-Grivn-Signature', '')
    timestamp = request.headers.get('X-Grivn-Timestamp', '')

    if not verify_webhook(body, signature, timestamp, WEBHOOK_SECRET):
        return 'Invalid signature', 401

    event = request.get_json()
    print(f"Event: {event['event_type']}")
    return 'OK', 200

Go

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "net/http"
)

func verifyWebhook(body, signature, timestamp, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(timestamp + "." + body))
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    signature := r.Header.Get("X-Grivn-Signature")
    timestamp := r.Header.Get("X-Grivn-Timestamp")

    if !verifyWebhook(string(body), signature, timestamp, webhookSecret) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    fmt.Println("Received webhook:", string(body))
    w.WriteHeader(http.StatusOK)
}

Retry & Delivery

A delivery is successful when your server responds with an HTTP 2xx status code within 10 seconds. If delivery fails, Grivn retries up to 3 times:

AttemptDelay
1stImmediate
2ndAfter 1 minute
3rdAfter 5 minutes

Delivery logs are available in the webhook settings page and retained for 7 days.

Limits

  • Maximum 5 webhooks per project
  • Webhook URL must use HTTPS
  • HTTP response timeout: 10 seconds
  • Delivery log retention: 7 days