Overview
Podium processes inbound webhooks from external services and uses a reliable async event system for internal event processing. All webhook endpoints are public (no API key required) but authenticated via service-specific signature verification.
External Webhooks
Stripe
Stripe webhooks handle payment lifecycle events. Each endpoint verifies the Stripe-Signature header against the configured webhook secret before processing.
| Endpoint | Event | What Happens |
|---|
/webhooks/stripe/payment-intent/succeeded | payment_intent.succeeded | Confirms order, triggers purchase-processed event, creates CreatorPayout |
/webhooks/stripe/payment-intent/processing | payment_intent.processing | Updates order status to PROCESSING |
/webhooks/stripe/payment-intent/payment-failed | payment_intent.payment_failed | Marks order payment as failed, triggers point revert |
/webhooks/stripe/account/updated | account.updated | Updates creator’s Stripe Connect account status |
/webhooks/stripe/transfer/created | transfer.created | Records payout transfer, updates CreatorPayout to TRANSFERRED |
Stripe Signature Verification
import Stripe from "stripe";
const stripe = new Stripe(STRIPE_SECRET_KEY);
const event = stripe.webhooks.constructEvent(
rawBody,
request.headers["stripe-signature"],
STRIPE_WEBHOOK_SECRET
);
Every Stripe webhook validates the signature before processing. If verification fails, the endpoint returns 400.
Example Payload: payment_intent.succeeded
{
"id": "evt_1234567890",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_1234567890",
"amount": 2900,
"currency": "usd",
"status": "succeeded",
"metadata": {
"orderId": "clord_xyz",
"userId": "clxyz1234567890",
"organizationId": "clorg_abc"
}
}
}
}
Shopify
Shopify sends webhooks to a single endpoint that dispatches based on the X-Shopify-Topic header:
| Topic | Handler | What Happens |
|---|
products/create | Sync product | Creates ShopifyProduct + linked Product |
products/update | Sync product | Updates product data, variants, media |
products/delete | Archive product | Soft-deletes the linked product |
app/uninstalled | Cleanup | Removes store connection |
Shopify webhook verification uses HMAC-SHA256 with the app’s shared secret against the X-Shopify-Hmac-Sha256 header.
Shopify HMAC Verification
import crypto from 'crypto';
function verifyShopifyWebhook(rawBody: string, hmacHeader: string, secret: string): boolean {
const hash = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(hmacHeader));
}
Privy
Privy sends blockchain transaction confirmations:
| Event Type | What Happens |
|---|
transaction.confirmed | Updates transaction status, processes pending operations |
transaction.execution_reverted | Marks transaction as failed, queues retry if applicable |
{
"chainId": 8453,
"eventType": "transaction.confirmed",
"txHash": "0xabc...",
"txId": "privy_tx_123",
"walletId": "privy_wallet_456"
}
Task Verification (Oracle)
The verification oracle callback is used by external verification services:
POST /webhooks/task-verify/{taskId}
{
"approved": true,
"resolverSignature": "0xabc...",
"timestamp": 1709827200,
"resolutionMetadata": {
"evaluationType": "ai",
"confidence": 0.95,
"notes": "Task output meets acceptance criteria"
}
}
| Field | Type | Required | Description |
|---|
approved | boolean | Yes | Whether the task passed verification |
resolverSignature | string | Yes | Oracle’s cryptographic signature |
timestamp | number | Yes | Unix timestamp of resolution |
resolutionMetadata | object | No | Additional verification context |
Async Event System
Podium’s async event system provides at-least-once delivery with automatic retries and dead-letter queues. All event handlers verify a cryptographic signature before processing.
Event Types (25 Queues)
Commerce Events
| Event | Trigger | Side Effects |
|---|
purchase-processed | Order payment confirmed | Award purchase points, update inventory, notify creator |
order-confirmation | Order created | Send confirmation email to buyer |
creator-order-confirmation | Order for a creator | Notify creator of new order via email/push |
shopify-order-push | Shopify-linked order | Push order data to connected Shopify store |
Campaign Events
| Event | Trigger | Side Effects |
|---|
campaign-published | Campaign goes live | Send notifications to followers |
campaign-ended | Campaign closes | Calculate final analytics, process reward distributions |
campaign-reviewed | Admin review completed | Notify creator of approval/rejection |
campaign-vote | New vote cast | Update attribute tallies, check for campaign completion |
Reward & Points Events
| Event | Trigger | Side Effects |
|---|
nft-received | Reward minted to user | Send notification to user |
nft-redeemed | Reward redeemed | Process redemption (create order, grant points, etc.) |
nft-reward-published | New reward available | Notify eligible users |
points-received | Points awarded | Send notification, update leaderboard |
airdrop | Airdrop triggered | Queue mints for all eligible users |
User & Social Events
| Event | Trigger | Side Effects |
|---|
user-created | New user registered | Send welcome email, create Stream Chat token |
follow | User followed a creator | Notify creator, update follower counts |
Product Events
| Event | Trigger | Side Effects |
|---|
product-published | Product goes live | Index in search, notify followers |
shopify-products-sync | Shopify sync triggered | Batch sync products from Shopify store |
| Event | Trigger | Side Effects |
|---|
api-key-changed | API key created/rotated/revoked | Flush caches across all instances |
stripe-onboarding-reminder | Creator hasn’t completed Stripe setup | Send reminder email |
presale-purchase-processed | Token presale purchase | Process presale reward, update presale state |
Task Pool Events
| Event | Trigger | Side Effects |
|---|
task-pool-deployed | New TaskPool deployed for tenant | Record contract addresses |
task-created | New task in pool | Notify registered solvers |
task-verification-requested | Task submitted for verification | Route to oracle or AI evaluator |
Enrichment Events
| Event | Trigger | Side Effects |
|---|
enrichment-crawl | Ingestion triggered | Fetch and process data from enrichment source |
enrichment-extract | Raw data ingested | Extract attributes via AI-powered extraction |
enrichment-baseline | Extraction complete | Recompute attribute baselines |
Retry & Idempotency
The event system provides automatic retries with exponential backoff:
- Max retries: 3 (configurable per queue)
- Backoff: Exponential with jitter
- Dead letter: Failed events after max retries are logged for manual review
The event system delivers at-least-once. Your handlers should be idempotent — processing the same event twice must produce the same result. Use database unique constraints and conditional updates to guard against duplicate processing.
Idempotency Patterns
// Use unique constraints to prevent duplicate processing
const existing = await db.creatorPayout.findUnique({
where: { orderId: event.orderId }
});
if (existing) return; // already processed
await db.creatorPayout.create({
data: { orderId: event.orderId, /* ... */ }
});
Cron Jobs (8 Scheduled Tasks)
| Job | Schedule | Purpose | Details |
|---|
airdrop-monitor | Every 5 min | Process pending airdrop deliveries | Checks Airdrop records with PENDING status, queues mints |
campaign-monitor | Every 15 min | End campaigns past their deadline | Sets ENDED status, triggers campaign-ended event |
contract-monitor | Every 10 min | Sync on-chain contract state | Verifies deployment status of reward contracts |
enrichment-baseline-recompute | Every 6 hours | Recompute stale attribute baselines | Recalculates AttributeBaseline values for enrichment |
mint-queue | Every 1 min | Process pending reward mints | Batches PENDING QueuedMints, executes via Privy server wallet |
payouts-sweep | Every 30 min | Transfer eligible creator payouts | Queries ELIGIBLE CreatorPayouts, executes Stripe transfers |
stripe-onboarding-reminder | Daily | Remind incomplete Stripe setups | Emails creators who started but didn’t finish Connect onboarding |
token-presale-monitor | Every 15 min | Monitor presale status changes | Checks presale deadlines and token distribution status |
All cron jobs are triggered by scheduled messages to their respective webhook endpoints.
Webhook Security Summary
| Source | Verification Method | Header |
|---|
| Stripe | HMAC-SHA256 with webhook secret | Stripe-Signature |
| Shopify | HMAC-SHA256 with app secret | X-Shopify-Hmac-Sha256 |
| Event System | Signing keys | Signature header |
| Privy | Privy webhook secret | Standard signature header |
| Task Oracle | Custom signature | resolverSignature in body |
Endpoint Summary
| Method | Path | Description |
|---|
POST | /webhooks/stripe/payment-intent/succeeded | Payment confirmed |
POST | /webhooks/stripe/payment-intent/processing | Payment processing |
POST | /webhooks/stripe/payment-intent/payment-failed | Payment failed |
POST | /webhooks/stripe/account/updated | Connect account update |
POST | /webhooks/stripe/transfer/created | Payout transferred |
POST | /webhooks/shopify | Shopify product/order events |
POST | /webhooks/privy | Blockchain transaction events |
POST | /webhooks/task-verify/{taskId} | Oracle verification callback |
POST | /webhooks/events/{event} | Async event handler (25 queues) |
POST | /webhooks/cron/{job} | Cron handler (8 jobs) |