Skip to main content

Digital Empire › Webhooks docs

Webhooks -- subscribe to portfolio events

Register a URL (Zapier "Catch Hook", n8n webhook node, Make custom webhook, or your own HTTP endpoint) to receive signed JSON callbacks whenever a scan finishes, an HTS code matches a new notice, an order ships, or a subscription event fires. HMAC-SHA256 signed. SSRF-hardened egress (private IPs and metadata endpoints are refused at registration time).

Explore

Quick start

  1. Open your Stripe Billing Portal magic link (from your welcome email).
  2. POST to /api/customer/webhooks with your email, token, target product, and webhook_url.
  3. The response includes a secret. Save it now -- it is shown once and used to verify every subsequent delivery.
  4. Point your endpoint at the code sample below for signature verification.

Event types

Every delivery uses the same envelope: { id, event, product, occurred_at, data }. Pass event_types: [] (or omit) to receive all events for the chosen product; pass an explicit list (e.g. ["pixelproof.scan.complete"]) to filter.

pixelproof.scan.completeproduct: pixelproof

Fires when a Chrome-extension scan finishes and its snapshot is written.

{
  "id": "evt_c7f4...",
  "event": "pixelproof.scan.complete",
  "product": "pixelproof",
  "occurred_at": "2026-08-26T12:34:56.000Z",
  "data": {
    "store_domain": "acme-cosmetics.myshopify.com",
    "scan_type": "pixel",
    "error_count": 2,
    "warn_count": 1
  }
}

tariffwatch.hts.matchedproduct: tariffwatch

Fires when a new BIS Federal Register notice matches one of the customer's watched HTS codes.

{
  "id": "evt_9a12...",
  "event": "tariffwatch.hts.matched",
  "product": "tariffwatch",
  "occurred_at": "2026-08-26T12:34:56.000Z",
  "data": {
    "hts_code": "7208.10.00",
    "notice_id": "2026-15961",
    "notice_title": "Section 232 derivative products expansion"
  }
}

entryproof.order.shippedproduct: entryproof

Fires when an EntryProof filing / GCC packet is packaged and dispatched to the customer.

{
  "id": "evt_4b8e...",
  "event": "entryproof.order.shipped",
  "product": "entryproof",
  "occurred_at": "2026-08-26T12:34:56.000Z",
  "data": {
    "order_id": "ord_ep_1a2b3c",
    "packet_type": "gcc"
  }
}

founding_trio.claimedproduct: all

Fires when a Founding Trio slot is claimed (Stripe checkout completed).

{
  "id": "evt_2d3e...",
  "event": "founding_trio.claimed",
  "product": "all",
  "occurred_at": "2026-08-26T12:34:56.000Z",
  "data": {
    "cohort_slot": 3,
    "cohort_cap": 5,
    "customer_email": "buyer@example.com",
    "primary_product": "pixelproof"
  }
}

Signature verification

Every request includes four headers you MUST verify before trusting the payload:

Node.js

import crypto from 'node:crypto';

function verify(req) {
  const sig = req.headers['x-digital-empire-signature']; // "sha256=<hex>"
  const ts  = req.headers['x-digital-empire-timestamp'];
  const raw = req.rawBody; // exact JSON body bytes we POSTed
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.DIGITAL_EMPIRE_WEBHOOK_SECRET)
    .update(`${ts}.${raw}`)
    .digest('hex');
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error('stale');
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) throw new Error('bad sig');
}

Python

import hmac, hashlib, os, time

def verify(headers, raw_body: bytes):
    sig = headers["X-Digital-Empire-Signature"]
    ts  = headers["X-Digital-Empire-Timestamp"]
    if abs(time.time() - int(ts)) > 300:
        raise ValueError("stale")
    expected = "sha256=" + hmac.new(
        os.environ["DIGITAL_EMPIRE_WEBHOOK_SECRET"].encode(),
        f"{ts}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        raise ValueError("bad sig")

Go

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "os"
    "strconv"
    "time"
)

func verify(headers map[string]string, rawBody []byte) error {
    sig := headers["X-Digital-Empire-Signature"]
    ts  := headers["X-Digital-Empire-Timestamp"]
    tsInt, _ := strconv.ParseInt(ts, 10, 64)
    if time.Now().Unix() - tsInt > 300 || tsInt - time.Now().Unix() > 300 {
        return fmt.Errorf("stale")
    }
    mac := hmac.New(sha256.New, []byte(os.Getenv("DIGITAL_EMPIRE_WEBHOOK_SECRET")))
    mac.Write([]byte(ts + "." + string(rawBody)))
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    if !hmac.Equal([]byte(sig), []byte(expected)) {
        return fmt.Errorf("bad sig")
    }
    return nil
}

Ruby

require 'openssl'

def verify(headers, raw_body)
  sig = headers['X-Digital-Empire-Signature']       # "sha256=<hex>"
  ts  = headers['X-Digital-Empire-Timestamp'].to_i
  raise 'stale' if (Time.now.to_i - ts).abs > 300

  expected = 'sha256=' + OpenSSL::HMAC.hexdigest(
    'SHA256',
    ENV.fetch('DIGITAL_EMPIRE_WEBHOOK_SECRET'),
    "#{ts}.#{raw_body}"
  )

  # Timing-safe equality via OpenSSL.
  raise 'bad sig' unless OpenSSL.fixed_length_secure_compare(sig, expected)
end

PHP

<?php
function verify(array $headers, string $rawBody): void {
    $sig = $headers['X-Digital-Empire-Signature'] ?? '';    // "sha256=<hex>"
    $ts  = (int) ($headers['X-Digital-Empire-Timestamp'] ?? 0);
    if (abs(time() - $ts) > 300) {
        throw new RuntimeException('stale');
    }
    $expected = 'sha256=' . hash_hmac(
        'sha256',
        $ts . '.' . $rawBody,
        getenv('DIGITAL_EMPIRE_WEBHOOK_SECRET') ?: ''
    );
    if (!hash_equals($expected, $sig)) {
        throw new RuntimeException('bad sig');
    }
}

Rate limits

Retry policy

Security

Related