Agent skill

Upollo Webhooks Skill

Receive and verify Upollo webhooks. Use when setting up Upollo webhook handlers, debugging Upollo-Signature verification (HMAC-SHA512), or reacting to fraud/risk flags like ACCOUNT_SHARING and MULTIPLE_ACCOUNTS when a user is flagged and an action (CHALLENGE, DENY, PERMIT) is returned.

Install this skill

npx skills add hookdeck/webhook-skills --skill upollo-webhooks


When to Use This Skill

  • How do I receive Upollo webhooks?
  • How do I verify the Upollo Upollo-Signature header?
  • How do I verify an Upollo HMAC-SHA512 signature over the raw body?
  • How do I react to an Upollo fraud flag like ACCOUNT_SHARING or MULTIPLE_ACCOUNTS?
  • How do I handle the Upollo action (CHALLENGE / DENY / PERMIT / OFFER / LOG)?
  • Why is my Upollo webhook signature verification failing?

How Upollo Webhooks Work

Upollo is a fraud / risk-detection platform. Unlike most providers, Upollo webhooks are not a subscription to discrete named events. A webhook fires whenever Upollo flags a user (for example account sharing or multi-accounting). Each delivery is an analysis describing the user, the device, the recommended action, and the list of flags that were raised.

Every delivery is signed. Upollo computes an HMAC-SHA512 of the raw request body, keyed with your webhook secret, and sends it in the Upollo-Signature header. Verify it against the raw body before doing anything else.

Not Standard Webhooks. Upollo does not use the Standard Webhooks (webhook-id / webhook-timestamp / webhook-signature) scheme. It uses a single Upollo-Signature header with t: and s0: parts (below).

Verification (core)

Upollo-Signature carries two comma-separated parts:

Upollo-Signature: t:1706352000,s0:3f9a...<128 hex chars>
  • t — Unix timestamp of the delivery (seconds). Use it for optional replay protection; it is not part of the signed content.
  • s0HMAC-SHA512(secret, rawBody). Recompute over the raw body and compare timing-safe.

Digest encoding. Upollo's docs don't state hex vs base64. The s0: prefix and observed 128-char values indicate lowercase hex. The snippets below compute the digest once and accept hex or base64 so they keep working either way — confirm hex against one live delivery, then you can drop base64.

Node:

const crypto = require('crypto');

function verifyUpolloWebhook(rawBody, signatureHeader, secret) {
  if (!signatureHeader) return false;
  const s0 = Object.fromEntries(
    signatureHeader.split(',').map((p) => {
      const i = p.indexOf(':');
      return [p.slice(0, i).trim(), p.slice(i + 1).trim()];
    })
  ).s0;
  if (!s0) return false;
  const digest = crypto.createHmac('sha512', secret).update(rawBody).digest();
  return [digest.toString('hex'), digest.toString('base64')].some((expected) => {
    try {
      return crypto.timingSafeEqual(Buffer.from(s0), Buffer.from(expected));
    } catch {
      return false; // length mismatch → not a match
    }
  });
}

Python:

import hmac, hashlib, base64

def verify_upollo_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header:
        return False
    parts = dict(p.split(":", 1) for p in signature_header.split(",") if ":" in p)
    s0 = parts.get("s0", "").strip()
    if not s0:
        return False
    digest = hmac.new(secret.encode(), raw_body, hashlib.sha512).digest()
    return (
        hmac.compare_digest(s0, digest.hex())
        or hmac.compare_digest(s0, base64.b64encode(digest).decode())
    )

For complete handlers with action dispatch, flag handling, and tests, see examples/express/, examples/nextjs/, examples/fastapi/.

The Payload: Action + Flags

Because there is no event name, dispatch on the recommended action and the flags array. The payload is Upollo's analysis object (fields shown in protobuf-JSON camelCase):

{
  "action": "CHALLENGE",
  "eventType": "LOGIN",
  "flags": [
    {
      "type": "ACCOUNT_SHARING",
      "firstFlagged": "2026-07-01T12:00:00Z",
      "mostRecentlyFlagged": "2026-07-27T09:00:00Z"
    }
  ],
  "userInfo": { "userId": "user_123", "userEmail": "user@example.com" },
  "deviceInfo": { "deviceId": "dev_abc", "deviceClass": "DEVICE_CLASS_DESKTOP" },
  "isUsingVpn": false
}

Upollo's enums carry prefixes at the source (OUTCOME_CHALLENGE, EVENT_TYPE_LOGIN, FLAG_TYPE_UNSPECIFIED); observed webhook payloads use the short form (CHALLENGE, LOGIN, ACCOUNT_SHARING). The example handlers normalize by stripping the OUTCOME_ / EVENT_TYPE_ / FLAG_TYPE_ prefix so they match either form.

Common Actions (action)

The recommended response to the flagged user. Values (short form):

ActionMeaningTypical Handling
PERMITAllow the user throughNo action
CHALLENGEStep-up verification recommendedTrigger MFA / email or SMS challenge
OFFERPresent an upsell / offerPrompt to upgrade (e.g. account sharing → add seats)
DENYBlock the actionReject login / purchase
LOGRecord onlyLog for review

Common Flags (flags[].type)

The reasons a user was flagged. Most-used values (short form):

FlagRaised When
ACCOUNT_SHARINGCredentials shared across users/households
ACCOUNT_SHARING_SAME_HOUSEHOLDSharing within one household
MULTIPLE_ACCOUNTSOne person operating multiple accounts
REPEATED_SIGNUPSame person signing up repeatedly
TRIALED_ON_OTHER_ACCOUNTFree trial already used on another account
REPEATED_REDEMPTIONOffer/coupon redeemed repeatedly
SUSPECTED_FRAUDGeneral fraud signal
SUSPECTED_BOTAutomated / bot behaviour
SUSPECTED_ACCOUNT_COMPROMISEPossible account takeover
CREDENTIAL_STUFFINGCredential-stuffing pattern
USING_VPN / USING_TORConnecting via VPN / Tor
DISPOSABLE_EMAILThrowaway email address

See references/overview.md for the full flag list.

Important Headers

HeaderDescription
Upollo-Signaturet:<unix_ts>,s0:<hmac-sha512 hex>. Verify s0 over the raw body

Environment Variables

# The webhook secret Upollo generated when you added your webhook URL under
# Webhooks on the Access & Keys page (app.upollo.ai).
UPOLLO_WEBHOOK_SECRET=your_webhook_secret_here

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 upollo --path /webhooks/upollo

Testing Flags

Upollo raises flags for specific test emails. Sign up / log in with a suffixed address to trigger a delivery:

  • you+account_sharing@example.comACCOUNT_SHARING
  • you+multiple_accounts@example.comMULTIPLE_ACCOUNTS

Reference Materials

Operational Status (verify before relying on this skill)

At the time of writing, app.upollo.ai / upollo.ai did not resolve, and the npm packages @upollo/web and @upollo/node return 404 (the PyPI upollo-python client SDK is still published). Upollo may be offline or have changed hands. Confirm Upollo is operational and re-verify the signature scheme and payload against a live delivery before depending on this integration. The verification scheme here is documented and matched to Upollo's own protobuf definitions but was not confirmed against a live payload.


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 6, 2026

View on GitHub →