Skip to main content

Webhooks

Webhooks allow you to receive real-time notifications when events occur in PriveTag, such as bookings being confirmed or vouchers being used.

Why Use Webhooks?

Real-time Updates

Get notified instantly when events happen, no polling required

Ground Truth Events

Know when users actually visit venues (QR verification)

Booking Lifecycle

Track booking from creation to completion

Analytics

Build custom analytics on user behavior

Setting Up Webhooks

Via Dashboard

  1. Go to privetag.com/developers/webhooks
  2. Click Add Endpoint
  3. Enter your webhook URL
  4. Select events to receive
  5. Copy and save your Webhook Secret

Via API Key Configuration

When creating or updating an API key, specify a webhook URL:
{
  "name": "Production Key",
  "webhook_url": "https://your-server.com/webhooks/privetag",
  "webhook_events": ["booking_confirmed", "voucher_delivered", "qr_verified"]
}

Webhook Events

EventDescriptionWhen Fired
booking_confirmedBooking successfully createdImmediately after /execute_booking
voucher_deliveredVoucher email sent to userWithin 30 seconds of booking
qr_verifiedUser visited venueWhen QR is scanned at venue
booking_cancelledBooking was cancelledOn cancellation
booking_modifiedBooking details changedOn modification

Webhook Payload

All webhooks follow this structure:
{
  "id": "wh_evt_abc123",
  "event": "booking_confirmed",
  "created_at": "2025-12-10T14:30:15Z",
  "data": {
    // Event-specific data
  }
}

booking_confirmed

{
  "id": "wh_evt_abc123",
  "event": "booking_confirmed",
  "created_at": "2025-12-10T14:30:15Z",
  "data": {
    "booking_id": "bkg_h7j9k2m4",
    "activity_id": "act_abc123",
    "activity_title": "Safari World Bangkok",
    "user_email": "guest@example.com",
    "user_name": "John Doe",
    "booking_date": "2025-12-15",
    "num_guests": 2,
    "total_price": 3000,
    "currency": "THB",
    "context_log_id": "ctx_xyz789"
  }
}

voucher_delivered

{
  "id": "wh_evt_def456",
  "event": "voucher_delivered",
  "created_at": "2025-12-10T14:30:45Z",
  "data": {
    "booking_id": "bkg_h7j9k2m4",
    "voucher_code": "PVT-2025-ABC123",
    "delivered_to": "guest@example.com",
    "delivered_at": "2025-12-10T14:30:43Z"
  }
}

qr_verified (Ground Truth)

This is the Ground Truth event - it tells you when a user actually visited the venue, not just when they booked.
{
  "id": "wh_evt_ghi789",
  "event": "qr_verified",
  "created_at": "2025-12-15T10:30:00Z",
  "data": {
    "booking_id": "bkg_h7j9k2m4",
    "activity_id": "act_abc123",
    "context_log_id": "ctx_xyz789",
    "verified_at": "2025-12-15T10:30:00Z",
    "venue_name": "Safari World Bangkok",
    "verification_method": "qr_scan"
  }
}

booking_cancelled

{
  "id": "wh_evt_jkl012",
  "event": "booking_cancelled",
  "created_at": "2025-12-12T09:15:00Z",
  "data": {
    "booking_id": "bkg_h7j9k2m4",
    "cancelled_at": "2025-12-12T09:15:00Z",
    "reason": "user_requested",
    "refund_status": "processed"
  }
}

Verifying Webhooks

All webhook requests include a signature header for verification:
X-Privetag-Signature: sha256=abc123...

Verification Example

import hmac
import hashlib

def verify_webhook(payload, signature, secret):
    expected = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(f"sha256={expected}", signature)

# In your webhook handler
@app.route('/webhooks/privetag', methods=['POST'])
def handle_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get('X-Privetag-Signature')

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

    event = request.json
    # Process event...
    return 'OK', 200
Always verify signatures to ensure webhooks are from PriveTag. Never process unverified webhooks.

Responding to Webhooks

Success Response

Return a 2xx status code to acknowledge receipt:
HTTP/1.1 200 OK

Retry Logic

If your endpoint returns a non-2xx status or times out, we’ll retry:
RetryDelay
1st5 seconds
2nd30 seconds
3rd2 minutes
4th10 minutes
5th1 hour
After 5 failed attempts, the webhook is marked as failed.

Best Practices

Return 200 immediately and process asynchronously:
@app.route('/webhooks/privetag', methods=['POST'])
def handle_webhook():
    event = request.json

    # Queue for async processing
    queue.enqueue(process_webhook, event)

    # Return immediately
    return 'OK', 200
Use id field for idempotency:
def process_webhook(event):
    event_id = event['id']

    # Check if already processed
    if redis.exists(f"webhook:{event_id}"):
        return

    # Process event
    handle_event(event)

    # Mark as processed (with TTL)
    redis.setex(f"webhook:{event_id}", 86400, '1')
Webhook URLs must use HTTPS in production. We verify SSL certificates.
Log all webhook events for debugging:
import logging

logger = logging.getLogger('webhooks')

def handle_webhook():
    event = request.json
    logger.info(f"Received webhook: {event['event']} - {event['id']}")
    # Process...

Testing Webhooks

Test Endpoint

Send test webhooks from the dashboard or via API:
curl -X POST https://api.privetag.com/api/webhooks/test \
  -H "x-api-key: pk_a1b2c3..." \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "booking_confirmed",
    "webhook_url": "https://your-server.com/webhooks/privetag"
  }'

Local Development

Use tools like ngrok or localtunnel to test webhooks locally:
# Start ngrok
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/privetag

Webhook Logs

View webhook delivery logs in the dashboard:
  • Delivery status: Success, failed, retrying
  • Response code: HTTP status returned
  • Response time: Latency of your endpoint
  • Payload: Full request sent

Event Filtering

Configure which events to receive:
{
  "webhook_url": "https://your-server.com/webhooks/privetag",
  "webhook_events": [
    "qr_verified"
  ]
}
This is useful for:
  • Only receiving Ground Truth events
  • Separating booking events from verification events
  • Reducing webhook volume

Next Steps