Custom Outbound Webhooks Reference
BASIC · PRO · PREMIUMReal-time event streaming for daily BaZi calendar shifts, solar term transitions, and quota alerts with HMAC-SHA256 signature verification.
REST API Reference
Outbound Webhooks are available across ALL paid plans: Basic ($19/mo) includes 1 active endpoint, Pro ($49/mo) includes 3 endpoints, and Premium ($149/mo) includes 10 endpoints. The BaZi API Webhook engine automatically pushes real-time JSON payloads to your servers whenever calendar events occur (such as the midnight BaZi Day Pillar shift, 24 solar term transitions, or quota threshold alerts). This allows mobile and web app backends to process automated daily updates to iOS and Android users via Firebase Cloud Messaging (FCM), Expo Notifications, or Apple APNs—without writing expensive 24/7 cron polling scripts.
Quick Start
Example Webhook Event Payload (daily.bazi_shift)
JSON
{
"event": "daily.bazi_shift",
"deliveryId": "del_01HXY89AB2CD3EF4GH5JK6MN",
"timestamp": "2026-09-05T00:00:00.000Z",
"data": {
"date": "2026-09-05",
"lunarDate": "丙午年 七月十六",
"dayPillar": {
"stem": "Bing",
"branch": "Wu",
"stemChar": "丙",
"branchChar": "午",
"element": "Fire / Fire",
"naYin": "Heavenly River Water"
},
"wuXing": {
"element": "Fire",
"polarity": "Yang"
},
"zodiac": "Horse",
"solarTerm": "White Dew (Bai Lu)"
}
}API Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
x-bazi-signature | string | Required | — | HMAC-SHA256 hex digest computed using your webhook signing secret (whsec_...) over the raw JSON body. |
x-bazi-event | string | Required | — | The event type name: 'daily.bazi_shift', 'solar_term.changed', 'usage.threshold_reached', or 'test.ping'. |
x-bazi-delivery-id | string | Required | — | Unique delivery UUID for idempotency and duplicate elimination. |
x-bazi-timestamp | string | Required | — | ISO-8601 UTC timestamp indicating exactly when the webhook payload was generated. |
More Examples
1. Backend Webhook Receiver & Mobile Push Dispatcher (Node.js + FCM)
1. Backend Webhook Receiver & Mobile Push Dispatcher (Node.js + FCM)
TypeScript
// server/src/routes/webhooks.ts
import express from "express";
import crypto from "crypto";
import admin from "firebase-admin";
const app = express();
app.use(express.json());
// Initialize Firebase Admin for mobile push notifications
// admin.initializeApp({ credential: admin.credential.applicationDefault() });
app.post("/api/webhooks/bazi", async (req, res) => {
const signature = req.headers["x-bazi-signature"] as string;
const event = req.headers["x-bazi-event"] as string;
const secret = process.env.BAZI_WEBHOOK_SECRET!; // whsec_...
// 1. Verify HMAC-SHA256 signature to prevent unauthorized calls
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(JSON.stringify(req.body))
.digest("hex");
if (!signature || signature !== expectedSignature) {
return res.status(401).json({ error: "Invalid webhook signature" });
}
// 2. Respond immediately with 200 OK within 5s
res.status(200).json({ received: true });
// 3. Handle midnight BaZi Day Pillar Shift event
if (event === "daily.bazi_shift") {
const { dayPillar, solarTerm } = req.body.data;
console.log(`[BaZi Shift] Today is ${dayPillar.stemChar}${dayPillar.branchChar} (${dayPillar.element})`);
// 4. Broadcast push notification to mobile app subscribers via FCM
try {
await admin.messaging().send({
topic: "daily-bazi-update",
notification: {
title: `Daily Pillar: ${dayPillar.stem} ${dayPillar.branch} (${dayPillar.element})`,
body: `Day Pillar transition to ${dayPillar.stemChar}${dayPillar.branchChar}. Solar Term: ${solarTerm}.`,
},
data: {
screen: "DailyPillar",
date: req.body.data.date,
element: dayPillar.element,
},
});
console.log("Successfully broadcasted daily BaZi calendar update to mobile users.");
} catch (err) {
console.error("Failed to broadcast FCM push notification:", err);
}
}
});2. React Native & Expo Mobile App Push Notification Handler
2. React Native & Expo Mobile App Push Notification Handler
TypeScript
// App.tsx or src/screens/DailyScreen.tsx in React Native / Expo
import React, { useEffect, useState } from "react";
import { View, Text, StyleSheet, SafeAreaView } from "react-native";
import * as Notifications from "expo-notifications";
// Configure how notifications are presented when app is foregrounded
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
export default function DailyBaZiScreen() {
const [dailyData, setDailyData] = useState<any>(null);
useEffect(() => {
// 1. Subscribe to push notifications topic
// Notifications.getDevicePushTokenAsync().then(...)
// 2. Listen for push notification received while app is running
const subscription = Notifications.addNotificationReceivedListener((notification) => {
const { title, body } = notification.request.content;
const customData = notification.request.content.data;
console.log("Received daily BaZi notification:", title, body, customData);
setDailyData({ title, body, ...customData });
});
// 3. Listen for user tapping on the push notification banner
const responseSub = Notifications.addNotificationResponseReceivedListener((response) => {
const data = response.notification.request.content.data;
if (data?.screen === "DailyPillar") {
// Navigate to Daily BaZi Details Screen
console.log("User tapped notification, navigate to details for date:", data.date);
}
});
return () => {
subscription.remove();
responseSub.remove();
};
}, []);
return (
<SafeAreaView style={styles.container}>
<Text style={styles.header}>Daily Four Pillars Shift</Text>
<View style={styles.card}>
<Text style={styles.title}>{dailyData?.title ?? "Waiting for midnight shift..."}</Text>
<Text style={styles.body}>{dailyData?.body ?? "Your daily calendar shift data will appear here automatically."}</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: "#070b16", padding: 20 },
header: { fontSize: 24, fontWeight: "bold", color: "#f5a800", marginBottom: 20 },
card: { backgroundColor: "#0e172a", padding: 20, borderRadius: 16, borderColor: "rgba(245,168,0,0.2)", borderWidth: 1 },
title: { fontSize: 16, fontWeight: "bold", color: "#ffffff", marginBottom: 8 },
body: { fontSize: 14, color: "#94a3b8", lineHeight: 22 },
});3. Flutter (Dart) FCM Background Notification Handler
3. Flutter (Dart) FCM Background Notification Handler
Dart
// lib/services/bazi_notification_service.dart in Flutter
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// Background message handler for FCM
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print("Handling BaZi background notification: ${message.messageId}");
}
class BaziNotificationService {
static Future<void> initialize() async {
// 1. Request user permission on iOS & Android 13+
FirebaseMessaging messaging = FirebaseMessaging.instance;
await messaging.requestPermission(alert: true, badge: true, sound: true);
// 2. Set background handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// 3. Subscribe to the Daily BaZi update topic
await messaging.subscribeToTopic("daily-bazi-update");
// 4. Foreground notification listener
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
if (notification != null) {
print("Received BaZi shift: ${notification.title}");
// Show local in-app banner or update UI state
}
});
}
}4. Next.js App Router Route Handler (TypeScript)
4. Next.js App Router Route Handler (TypeScript)
TypeScript
// app/api/webhooks/bazi/route.ts
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
export async function POST(req: NextRequest) {
const signature = req.headers.get("x-bazi-signature");
const event = req.headers.get("x-bazi-event");
const deliveryId = req.headers.get("x-bazi-delivery-id");
const secret = process.env.BAZI_WEBHOOK_SECRET!; // whsec_...
// 1. Read raw text body for HMAC verification
const rawBody = await req.text();
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// 2. Check cryptographic signature
if (!signature || signature !== expectedSignature) {
return NextResponse.json({ error: "Invalid HMAC-SHA256 signature" }, { status: 401 });
}
// 3. Parse JSON payload
const payload = JSON.parse(rawBody);
// 4. Handle BaZi events
if (event === "daily.bazi_shift") {
const { dayPillar, solarTerm } = payload.data;
console.log(`Daily shift: ${dayPillar.stem} ${dayPillar.branch} (${dayPillar.element})`);
// Broadcast notification or sync to database
}
// 5. Always acknowledge with 200 OK within 5s
return NextResponse.json({ received: true });
}5. Python (FastAPI) Webhook Signature Verification
5. Python (FastAPI) Webhook Signature Verification
Python
from fastapi import FastAPI, Request, Header, HTTPException
import hmac
import hashlib
import json
import os
app = FastAPI()
WEBHOOK_SECRET = os.getenv("BAZI_WEBHOOK_SECRET") # whsec_...
@app.post("/api/webhooks/bazi")
async def handle_bazi_webhook(request: Request, x_bazi_signature: str = Header(None)):
raw_body = await request.body()
expected = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
if not x_bazi_signature or not hmac.compare_digest(x_bazi_signature, expected):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = json.loads(raw_body)
print("Received verified BaZi event:", payload.get("event"))
return {"received": True}6. PHP Native Webhook Receiver
6. PHP Native Webhook Receiver
php
<?php
$secret = getenv('BAZI_WEBHOOK_SECRET');
$signature = $_SERVER['HTTP_X_BAZI_SIGNATURE'] ?? '';
$rawBody = file_get_contents('php://input');
$expected = hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
$payload = json_decode($rawBody, true);
// Process event data (broadcast push notification / update database)
http_response_code(200);
echo json_encode(['received' => true]);7. Programmatic Webhook Management API (cURL)
7. Programmatic Webhook Management API (cURL)
Bash
# 1. Register a new outbound webhook endpoint (Basic: 1, Pro: 3, Premium: 10)
curl -X POST https://api.baziapi.pro/api/v1/custom-webhooks \
-H "x-api-key: bazi_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/api/webhooks/bazi",
"events": ["daily.bazi_shift", "solar_term.changed"],
"description": "Production mobile app push notifications server"
}'
# 2. List all registered webhook subscriptions
curl -X GET https://api.baziapi.pro/api/v1/custom-webhooks \
-H "x-api-key: bazi_live_your_key_here"
# 3. Trigger on-demand test ping
curl -X POST https://api.baziapi.pro/api/v1/custom-webhooks/{id}/test \
-H "x-api-key: bazi_live_your_key_here"Something unclear? Contact developer support.