juicebox-webhooks-events

Handle Juicebox webhooks and events. Trigger: "juicebox webhooks", "juicebox events".

Allowed Tools

ReadWriteEditGrep

Provided by Plugin

juicebox-pack

Claude Code skill pack for Juicebox (24 skills)

saas packs v1.16.0
View Plugin

Installation

This skill is included in the juicebox-pack plugin:

/plugin install juicebox-pack@claude-code-plugins-plus

Click to copy

Instructions

Juicebox Webhooks & Events

Overview

Juicebox delivers webhook notifications for AI-powered people search and analysis workflows. Subscribe to events for completed analyses, updated datasets, ready exports, and quota warnings to build automated pipelines that react to Juicebox intelligence in real time without polling the API.

Webhook Registration


const response = await fetch("https://api.juicebox.ai/v1/webhooks", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.JUICEBOX_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://yourapp.com/webhooks/juicebox",
    events: ["analysis.completed", "dataset.updated", "export.ready", "quota.warning"],
    secret: process.env.JUICEBOX_WEBHOOK_SECRET,
  }),
});

Signature Verification


import crypto from "crypto";
import { Request, Response, NextFunction } from "express";

function verifyJuiceboxSignature(req: Request, res: Response, next: NextFunction) {
  const signature = req.headers["x-juicebox-signature"] as string;
  const expected = crypto.createHmac("sha256", process.env.JUICEBOX_WEBHOOK_SECRET!)
    .update(req.body).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).json({ error: "Invalid signature" });
  }
  next();
}

Event Handler


import express from "express";
const app = express();

app.post("/webhooks/juicebox", express.raw({ type: "application/json" }), verifyJuiceboxSignature, (req, res) => {
  const event = JSON.parse(req.body.toString());
  res.status(200).json({ received: true });

  switch (event.type) {
    case "analysis.completed":
      fetchResults(event.data.analysis_id, event.data.result_count); break;
    case "dataset.updated":
      refreshDashboard(event.data.dataset_id, event.data.records_added); break;
    case "export.ready":
      downloadExport(event.data.export_id, event.data.download_url); break;
    case "quota.warning":
      notifyAdmin(event.data.usage_percent, event.data.reset_date); break;
  }
});

Event Types

Event Payload Fields Use Case
analysis.completed analysis_id, result_count, query Fetch and process search results
dataset.updated dataset_id, records_added, total_records Refresh downstream dashboards
export.ready export_id, download_url, format Auto-download CSV/JSON exports
quota.warning usage_percent, reset_date, plan_tier Alert admin before limit hit
search.alert alert_id, new_matches, criteria Notify on new candidate matches

Retry & Idempotency


const processed = new Set<string>();

async function handleIdempotent(event: { id: string; type: string; data: any }) {
  if (processed.has(event.id)) return;
  await routeEvent(event);
  processed.add(event.id);
  if (processed.size > 10_000) {
    const entries = Array.from(processed);
    entries.slice(0, entries.length - 10_000).forEach((id) => processed.delete(id));
  }
}

Error Handling

Issue Cause Fix
Signature mismatch Secret rotated without updating handler Re-fetch secret from Juicebox dashboard
Empty result_count Analysis timed out Check analysis status before processing
Export link expired Download URL has 1-hour TTL Fetch immediately on export.ready event
Quota exceeded API calls after limit hit Implement backoff until reset_date

Prerequisites

  • A secret-manager webhook secret, event-origin allowlist, replay-window policy, source-authority check, suppression policy, and opaque event ledger.

Instructions

  1. Authenticate origin before parsing, validate timestamp/event ID, and reject unknown sources, stale deliveries, and malformed payloads.
  2. Store only a bounded redacted envelope and use event ID plus target revision as the idempotency key.
  3. Verify source authority, suppression, and destination before enqueueing enrichment; quarantine uncertainty instead of exporting or enriching a contact.
  4. Canary one sandbox source, observe aggregate policy probes, then promote or disable the consumer.
  5. Route exhausted retries to reviewed recovery without replaying non-idempotent writes.

Output

Return an event receipt with type, opaque ID, signature/timestamp result, source/destination, suppression result, idempotency outcome, export-count assertion, queue state, and rollback reference. Exclude payload contact data and signatures.

Examples

type=prospect.updated; event=evt-opaque-9; signature=pass; replay=absent; source=synthetic; suppression=pass; contacts_exported=0; rollback=consumer-disabled proves the event boundary.

Resources

  • Juicebox API Docs

Next Steps

See juicebox-security-basics.

Ready to use juicebox-pack?