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:
| Event | Description | Delivery |
|---|---|---|
deeplink.click | A deeplink was clicked | Batched (30s window) |
deeplink.install | App install confirmed via SDK | Immediate |
deeplink.create | A new deeplink was created | Immediate |
deeplink.update | A deeplink was modified | Immediate |
deeplink.delete | A deeplink was deleted | Immediate |
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 formatsha256=<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', 200Go
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:
| Attempt | Delay |
|---|---|
| 1st | Immediate |
| 2nd | After 1 minute |
| 3rd | After 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