Webhooks
Subscribe to real-time HTTP POST notifications when new emails arrive in your registered inboxes. Webhooks require the Growth plan or above.
POST /v1/webhooks
Register a webhook URL for a specific inbox. The URL must be publicly reachable over HTTPS.
curl -X POST https://api2.freecustom.email/v1/webhooks \
-H "Authorization: Bearer fce_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/callback",
"inbox": "target@ditapi.info"
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
| url | string (uri) | ✓ | HTTPS endpoint to receive POST requests. |
| inbox | string (email) | ✓ | A registered inbox under your account. |
Responses
201Webhook registered
{
"success": true,
"id": "wh_01jqz4abc123",
"inbox": "target@ditapi.info",
"url": "https://your-server.com/callback"
}403Plan too low or inbox not owned
{
"success": false,
"error": "plan_required",
"message": "Webhooks require the Growth plan ($49/mo) or above.",
"upgrade_url": "https://freecustom.email/api/pricing"
}GET /v1/webhooks
Returns all active webhook subscriptions for your account.
curl "https://api2.freecustom.email/v1/webhooks" \
-H "Authorization: Bearer fce_your_api_key"200Success
{
"success": true,
"count": 2,
"data": [
{
"_id": "wh_01jqz4abc123",
"inbox": "target@ditapi.info",
"url": "https://your-server.com/callback",
"createdAt": "2026-03-04T10:00:00.000Z",
"failureCount": 0
}
]
}DELETE /v1/webhooks/{id}
Unregisters a webhook by its ID. The ID is returned when you register a webhook.
curl -X DELETE "https://api2.freecustom.email/v1/webhooks/wh_01jqz4abc123" \
-H "Authorization: Bearer fce_your_api_key"200Deleted
{
"success": true,
"message": "Webhook wh_01jqz4abc123 unregistered."
}404Not found
{
"success": false,
"error": "not_found",
"message": "Webhook wh_01jqz4abc123 not found."
}Webhook Payload
When a new email arrives in a subscribed inbox, we send a POST request to your URL with the following JSON body:
{
"event": "new_message",
"inbox": "target@ditapi.info",
"message": {
"id": "msg_01jqz3k4m5n6p7q8",
"from": "noreply@github.com",
"subject": "Your GitHub verification code",
"date": "2026-03-04T09:55:00.000Z",
"has_attachment": false,
"otp": "482931",
"verification_link": "https://github.com/verify?token=abc123"
}
}Respond with any 2xx status to acknowledge receipt. We retry up to 3 times with exponential backoff on failure. After 10 consecutive failures the webhook is automatically disabled.
Security
All webhook requests include an X-FCE-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your API key. Verify it to ensure the request originated from FreeCustom.Email:
import crypto from "crypto";
function verifyWebhook(rawBody: string, signature: string, apiKey: string) {
const expected = crypto
.createHmac("sha256", apiKey)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex")
);
}Integration Examples
Node.js / Express
import express from "express";
import crypto from "crypto";
const app = express();
app.use(express.raw({ type: "application/json" }));
app.post("/callback", (req, res) => {
const sig = req.headers["x-fce-signature"] as string;
const raw = req.body.toString();
if (!verifyWebhook(raw, sig, process.env.FCE_API_KEY!)) {
return res.status(401).send("Invalid signature");
}
const { event, inbox, message } = JSON.parse(raw);
console.log(`New email in ${inbox}: OTP = ${message.otp}`);
res.sendStatus(200);
});Python / FastAPI
import hmac, hashlib, os
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post("/callback")
async def webhook(request: Request):
body = await request.body()
sig = request.headers.get("x-fce-signature", "")
expected = hmac.new(
os.environ["FCE_API_KEY"].encode(),
body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, sig):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = await request.json()
print(f"OTP: {payload['message']['otp']}")
return {"ok": True}