Webhooks

Webhooks allow you to receive real-time notifications when consent events occur in Redacto CMP. This enables you to sync consent status with your systems, trigger workflows, and maintain audit trails.

Overview

When a consent event occurs (e.g., user grants consent), Redacto sends an HTTP POST request to your configured webhook endpoint with event details.

┌─────────────┐         ┌─────────────────┐         ┌──────────────┐
│   User      │────────▶│  Redacto CMP    │────────▶│ Your Webhook │
│   Action    │         │  (Event Occurs) │         │   Endpoint   │
└─────────────┘         └─────────────────┘         └──────────────┘

Event Types

Redacto supports the following webhook event types:

Consent Events

EventDescription
consent.event.grantedUser granted consent for all purposes
consent.event.deniedUser explicitly denied consent
consent.event.partially_grantedUser granted consent for some purposes
consent.event.withdrawnUser withdrew previously given consent
consent.event.renewUser renewed their consent
consent.event.expiredConsent expired based on validity period

Purpose Events

EventDescription
consent.purpose.withdrawnConsent withdrawn for a specific purpose
consent.purpose.expiredConsent expired for a specific purpose

Payload Structure

Consent Event Payload

When a consent event occurs, you receive the following payload:

{
  "event_type": "consent.event.granted",
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "consent_event_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "organisation_uuid": "org-uuid-here",
    "workspace_uuid": "ws-uuid-here",
    "notice_uuid": "notice-uuid-here",
    "notice_version": 1,
    "source": "web_sdk",
    "status": "granted",
    "chain_position": 1,
    "meta_data": {
      "user_agent": "Mozilla/5.0...",
      "ip_address": "192.168.1.1"
    },
    "org_user_id": "user123",
    "primary_email": "[email protected]",
    "primary_mobile": "+1234567890",
    "created_at": "2024-01-15T10:30:00Z",
    "campaign_uuid": null
  }
}

Payload Fields:

FieldTypeDescription
event_typestringThe type of event that occurred
event_idstringUnique identifier for this event (for deduplication)
timestampstringISO 8601 timestamp of when the event occurred
data.consent_event_uuidstringUUID of the consent event record
data.organisation_uuidstringYour organisation UUID
data.workspace_uuidstringThe workspace UUID
data.notice_uuidstringThe consent notice UUID
data.notice_versionnumberVersion of the consent notice
data.sourcestringWhere the consent was collected
data.statusstringConsent status (granted, denied, etc.)
data.chain_positionnumberPosition in the consent chain
data.meta_dataobjectAdditional context about the consent
data.org_user_idstringYour user identifier (if provided)
data.primary_emailstringUser's email (if provided)
data.primary_mobilestringUser's phone (if provided)
data.created_atstringWhen the consent was recorded
data.campaign_uuidstringAssociated campaign (if any)

Purpose Event Payload

When a purpose-level event occurs:

{
  "event_type": "consent.purpose.withdrawn",
  "event_id": "660e8400-e29b-41d4-a716-446655440001",
  "timestamp": "2024-01-15T11:00:00Z",
  "data": {
    "purpose_uuid": "purpose-uuid-here",
    "name": "Marketing Communications",
    "status": "withdrawn",
    "validity": "1 year",
    "expiry_datetime": "2025-01-15T10:30:00Z",
    "org_user_id": "user123",
    "primary_email": "[email protected]",
    "primary_mobile": "+1234567890",
    "created_at": "2024-01-15T11:00:00Z"
  }
}

Purpose Payload Fields:

FieldTypeDescription
data.purpose_uuidstringUUID of the purpose
data.namestringName of the purpose
data.statusstringCurrent status (withdrawn, expired)
data.validitystringValidity period of the consent
data.expiry_datetimestringWhen the consent expires

Setup

1. Create a Webhook Endpoint

Create an endpoint in your application to receive webhook events:

// Express.js example
app.post("/webhooks/redacto", express.json(), (req, res) => {
  const event = req.body;

  console.log(`Received event: ${event.event_type}`);
  console.log(`Event ID: ${event.event_id}`);

  // Process the event
  switch (event.event_type) {
    case "consent.event.granted":
      handleConsentGranted(event.data);
      break;
    case "consent.event.withdrawn":
      handleConsentWithdrawn(event.data);
      break;
    case "consent.purpose.withdrawn":
      handlePurposeWithdrawn(event.data);
      break;
    // ... handle other events
  }

  // Acknowledge receipt
  res.status(200).json({ received: true });
});

2. Configure in Redacto Console

  1. Log in to the Redacto Console
  2. Navigate to Integrations > Webhooks
  3. Click Add Webhook
  4. Enter your endpoint URL
  5. Select the events you want to receive
  6. Save the configuration

3. Verify Your Endpoint

Redacto will send a test event to verify your endpoint is working. Ensure your endpoint:

  • Responds with a 2xx status code
  • Responds within 30 seconds
  • Returns a JSON response

Handling Webhooks

Idempotency

Each event includes a unique event_id. Use this for deduplication:

async function handleWebhook(event) {
  // Check if we've already processed this event
  const processed = await db.webhookEvents.findOne({
    event_id: event.event_id,
  });

  if (processed) {
    console.log(`Event ${event.event_id} already processed`);
    return;
  }

  // Process the event
  await processEvent(event);

  // Mark as processed
  await db.webhookEvents.create({
    event_id: event.event_id,
    processed_at: new Date(),
  });
}

Error Handling

If your endpoint fails to respond with a 2xx status code, Redacto will retry the webhook:

  • Retries are attempted with exponential backoff
  • Maximum of 3 retry attempts
  • After all retries fail, the event is logged for manual review
app.post("/webhooks/redacto", async (req, res) => {
  try {
    await processEvent(req.body);
    res.status(200).json({ success: true });
  } catch (error) {
    console.error("Webhook processing failed:", error);

    // Return 500 to trigger retry
    res.status(500).json({ error: "Processing failed" });
  }
});

Async Processing

For long-running operations, acknowledge the webhook immediately and process asynchronously:

app.post("/webhooks/redacto", async (req, res) => {
  // Immediately acknowledge receipt
  res.status(200).json({ received: true });

  // Queue for async processing
  await messageQueue.publish("consent-events", req.body);
});

// Separate worker processes the queue
messageQueue.subscribe("consent-events", async (event) => {
  await processEvent(event);
});

Use Cases

Sync Consent to CRM

async function handleConsentGranted(data) {
  const { org_user_id, primary_email, status } = data;

  await crm.updateContact(org_user_id, {
    consent_status: status,
    consent_date: data.created_at,
    marketing_opt_in: true,
  });
}

Update Email Preferences

async function handlePurposeWithdrawn(data) {
  if (data.name === "Marketing Communications") {
    await emailService.unsubscribe(data.primary_email);
  }
}

Audit Logging

async function logConsentEvent(event) {
  await auditLog.create({
    event_type: event.event_type,
    user_id: event.data.org_user_id,
    email: event.data.primary_email,
    consent_status: event.data.status,
    notice_uuid: event.data.notice_uuid,
    timestamp: event.timestamp,
    source: event.data.source,
  });
}

Trigger Workflows

async function handleConsentGranted(data) {
  // Trigger welcome email workflow
  if (data.source === "signup_form") {
    await workflows.trigger("welcome-sequence", {
      email: data.primary_email,
      user_id: data.org_user_id,
    });
  }
}

Security Considerations

Verify Webhook Origin

Ensure webhooks come from Redacto by checking the source IP or implementing signature verification if available.

Use HTTPS

Always use HTTPS for your webhook endpoint to ensure data is encrypted in transit.

Validate Payload

Validate the webhook payload before processing:

function validatePayload(event) {
  if (!event.event_type || !event.event_id || !event.data) {
    throw new Error("Invalid webhook payload");
  }

  // Verify organisation_uuid matches your account
  if (event.data.organisation_uuid !== YOUR_ORG_UUID) {
    throw new Error("Organisation mismatch");
  }
}