Skip to main content
Early Access: Webhook payload normalization is currently in active development. Payment normalization is available now via @hookflo/tern. Additional categories (Auth, Database, Infrastructure) are coming soon.

The Problem

Your app integrates with Stripe for payments. Then a customer wants Razorpay. Another wants PayPal. Each platform sends webhooks in completely different formats:
{
  "id": "ch_3NqBXY2eZvKYlo2C0wB6xJZv",
  "object": "charge",
  "amount": 5000,
  "amount_captured": 5000,
  "currency": "usd",
  "status": "succeeded",
  "payment_method_details": {
    "card": {
      "brand": "visa",
      "last4": "4242"
    }
  },
  "metadata": {...},
  "billing_details": {...}
  // ... 50+ more fields
}
Now you’re maintaining separate handlers for each platform. Migration? Rewrite everything.

The Solution

Hookflo’s @hookflo/tern normalizes all webhook payloads into consistent schemas:
import { normalize } from '@hookflo/tern';

app.post('/webhooks/:platform', async (req, res) => {
  // Works with Stripe, Razorpay, PayPal, Square...
  const payment = normalize(req.body, {
    platform: req.params.platform,
    category: 'payments' // ✅ Available now
  });
  
  // Always get the same structure
  await processPayment({
    id: payment.transaction_id,
    amount: payment.amount,
    currency: payment.currency,
    status: payment.status,
    method: payment.payment_method
  });
});

Benefits

1. Switch Platforms Without Code Changes

The Impact: Migrate from one provider to another in minutes, not months. Testing a new payment processor? Want to switch auth providers? With normalized payloads, your webhook handlers work with any platform immediately. Before Hookflo:
  • 2-3 weeks of development per migration
  • High risk of bugs in rewritten handlers
  • Business disruption during transition
  • Vendor lock-in due to switching costs
With Hookflo:
  • Change a config value
  • Zero code modifications
  • Test in staging instantly
  • True multi-vendor freedom

2. Reduce Bandwidth by 40-70%

The Impact: Significant cost savings at scale, faster processing, better performance. Raw webhooks include dozens of unnecessary fields. Stripe sends 50+ fields per payment webhook — you need maybe 8. That’s 80% waste.
// Raw Stripe payload: ~2.5 KB
{
  "id": "ch_...",
  "object": "charge",
  "amount": 5000,
  // ... 45 more fields you never use
  "metadata": {...},
  "refunds": {...},
  "fraud_details": {...}
}

// Normalized payload: ~0.4 KB  
{
  "transaction_id": "ch_...",
  "amount": 5000,
  "currency": "usd",
  "status": "succeeded",
  "customer_id": "cus_...",
  "payment_method": "card",
  "timestamp": "2024-10-10T12:00:00Z"
}
At 1M webhooks/month:
  • Raw data: ~2.5 TB/month
  • Normalized: ~0.4 TB/month
  • Savings: 2.1 TB/month
This translates to lower infrastructure costs, faster API responses, and reduced network load.

3. Integrate New Platforms in Hours, Not Weeks

The Impact: 10x faster time-to-market for new integrations. Traditional approach: Study docs → Parse responses → Map fields → Write tests → Deploy → Repeat for next platform. With normalized schemas: Install package → Use existing handlers → Done.

Payments

Stripe, PayPal, Razorpay, Polar

Authentication

Auth0, Okta, Clerk, Firebase Auth, Cognito

Database

MongoDB, Supabase, PostgreSQL

Infrastructure

AWS, Vercel, Railway, Render
One codebase handles them all. Add a new provider by changing one line:
- platform: 'stripe'
+ platform: 'razorpay'

4. Crystal Clear Data, Zero Ambiguity

The Impact: Write less code, prevent bugs, onboard developers faster. Different platforms use different field names for the same concept:
  • Amount: amount vs value vs total
  • Status: status vs state vs payment_status
  • User ID: customer_id vs user_id vs customer
Hookflo standardizes everything:
  • Payments Schema
  • Auth Schema
  • Database Schema
interface NormalizedPayment {
  transaction_id: string;
  amount: number;           // Always in smallest currency unit
  currency: string;          // Always ISO 4217 (USD, EUR, INR)
  status: 'succeeded' | 'failed' | 'pending' | 'refunded';
  customer_id: string;
  payment_method: 'card' | 'bank_transfer' | 'wallet' | 'other';
  timestamp: string;         // Always ISO 8601
}
Benefits:
  • Type-safe across all platforms
  • Self-documenting code
  • Predictable behavior
  • No more “wait, which field has the amount?” moments

5. Keep Raw Payloads When You Need Them

The Impact: Get normalization benefits without losing flexibility. Sometimes you need platform-specific data. Hookflo gives you both:
const result = normalize(req.body, {
  platform: 'stripe',
  category: 'payments',
  mode: 'hybrid'  // Get both normalized and raw
});

// Use normalized for business logic
await processPayment(result.normalized);

// Access raw for platform-specific features
if (result.raw.payment_method_details?.card?.brand === 'amex') {
  await applyAmexPerks(result.normalized.customer_id);
}

// Perfect for debugging
logger.debug('Full webhook payload:', result.raw);
Smart Migration: Transition legacy systems gradually by keeping raw payloads accessible while moving to normalized data.

Supported Categories

Platforms: Stripe, PayPal, Square, Razorpay, Adyen, Braintree, Paddle, MollieNormalized Fields:
  • Transaction ID & amount
  • Currency (ISO 4217)
  • Status (succeeded, failed, pending, refunded)
  • Customer ID
  • Payment method type
  • Timestamp (ISO 8601)
Planned Platforms: Auth0, Okta, Clerk, Firebase Auth, AWS Cognito, WorkOSPlanned Normalized Fields:
  • User ID
  • Event type (login, logout, signup, password_reset, mfa_enabled)
  • Success indicator
  • IP address & device type
  • Timestamp
Planned Platforms: MongoDB, Supabase, PostgreSQL, PlanetScale, Firebase, NeonPlanned Normalized Fields:
  • CRUD operation type
  • Collection/table name
  • Record ID
  • Change summary
  • Actor (user_id)
  • Timestamp
Planned Platforms: AWS, Google Cloud, Azure, Vercel, Railway, Render, Fly.ioPlanned Normalized Fields:
  • Resource identifier
  • Event type (deployment, scaling, failure)
  • Status & severity level
  • Region/zone
  • Timestamp
Want to influence our roadmap? Join our Discord or open a discussion on GitHub to request specific platforms or categories.

Getting Started

1

Install the package

npm install @hookflo/tern
2

Normalize your first webhook

import { normalize } from '@hookflo/tern';

const payment = normalize(webhookPayload, {
  platform: 'stripe',
  category: 'payments'
});
3

Use consistent data everywhere

// Works with Stripe, Razorpay, PayPal, Square...
console.log(payment.transaction_id);
console.log(payment.amount);
console.log(payment.status);

Before vs After

Without Hookflo

  • Separate handler per platform
  • 2-3 weeks per integration
  • Platform migrations = full rewrites
  • 100% payload size
  • Vendor lock-in
  • Complex testing

With Hookflo

  • Single unified handler
  • Hours per integration
  • Change config to migrate
  • 30-60% payload size
  • Zero vendor lock-in
  • Simple, consistent tests

FAQ

Payment platforms are fully supported including Stripe, PayPal, Square, Razorpay, Adyen, and Braintree. Auth, Database, and Infrastructure categories are in development.
Use mode: 'hybrid' to get both normalized and raw payloads. You get the convenience of normalized data with full access to platform-specific fields when needed.
Yes! While we provide pre-built schemas for 50+ platforms, you can define custom normalization rules for internal services or less common platforms.
Negligible. Normalization adds ~1-2ms of processing time but saves significantly more through reduced payload size and faster parsing.
Absolutely. Use hybrid mode to keep raw payloads while transitioning your codebase. Migrate endpoint by endpoint at your own pace.
We monitor platform APIs and update normalization rules automatically. Your code stays unchanged even when providers change their webhook formats.
Yes! We’re actively building our roadmap based on user feedback. Join our Discord or open a GitHub discussion to request features.

Start Normalizing Today

Stop maintaining separate webhook handlers for every platform. Write once, integrate everywhere.
I