Webhooks
Instead of asking whether mail has arrived, get told. We POST to your endpoint the moment a message reaches any of your API addresses.
When to use them
For a test that is waiting on one email, ?wait= or /code is simpler, because it's a single
request that returns when the mail lands. Webhooks suit everything else: a service that processes
inbound mail, a queue worker, a dashboard, or many inboxes at once.
1. Register an endpoint
curl -X POST https://api.mailfornet.com/v1/webhooks \
-H "Authorization: Bearer mf_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://yourapp.com/hooks/mailfornet"}'
{"id": 12, "url": "https://yourapp.com/hooks/mailfornet", "secret": "whsec_…", "createdAt": "…"}
The secret is shown once. Store it with your other secrets. You can also create and
manage endpoints in your account under Webhooks.
2. What we send
POST /hooks/mailfornet
content-type: application/json
user-agent: Mailfornet-Webhook/1
mailfornet-event: message.received
mailfornet-timestamp: 1790812800
mailfornet-signature: 5f2b…c91e
{
"event": "message.received",
"createdAt": "2026-09-25T17:12:06.838Z",
"data": {
"address": "ryan.grant688@tikaurasave.site",
"from": "no-reply@github.com",
"subject": "Your GitHub launch code",
"intro": "Enter code 731905 to finish…",
"receivedAt": "2026-09-25T17:12:06.838Z"
}
}
The payload is a notification, not the whole message. To get the body, the extracted code and the
links, call GET /v1/inboxes/{address}/code or list the messages and read the one you need.
3. Verify the signature
The signature is a hex HMAC-SHA256 of ${timestamp}.${rawBody}, using your endpoint's secret.
Check it against the raw body. If you parse the JSON and serialise it again, the bytes change
and the signature won't match. Reject timestamps more than five minutes old, so a captured delivery
can't be replayed later.
Node.js, with the official client:
import express from 'express';
import { verifyWebhook } from 'mailfornet';
const app = express();
app.post('/hooks/mailfornet', express.text({ type: '*/*' }), async (req, res) => {
const ok = await verifyWebhook({
secret: process.env.MAILFORNET_WEBHOOK_SECRET,
body: req.body, // the raw string
signature: req.get('mailfornet-signature'),
timestamp: req.get('mailfornet-timestamp'),
});
if (!ok) return res.sendStatus(401);
const { event, data } = JSON.parse(req.body);
if (event === 'message.received') queue.add(data);
res.sendStatus(200);
});
Python (Flask):
import hmac, hashlib, os, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["MAILFORNET_WEBHOOK_SECRET"].encode()
@app.post("/hooks/mailfornet")
def mailfornet():
ts = request.headers.get("mailfornet-timestamp", "")
sig = request.headers.get("mailfornet-signature", "")
raw = request.get_data() # bytes, exactly as sent
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(401)
expected = hmac.new(SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
abort(401)
event = request.get_json()
# … handle event["data"]
return "", 200
4. Test without sending an email
curl -X POST https://api.mailfornet.com/v1/webhooks/12/test \
-H "Authorization: Bearer mf_live_your_key"
This sends a signed webhook.test event to that one endpoint and returns the status your server
answered with, so you can check your signature code before any real mail flows.
Delivery rules
- Answer with any 2xx within 10 seconds. Do slow work in the background, after you respond.
- Every attempt is recorded.
GET /v1/webhooks/{id}/deliveriesshows the last 20, with the status we got back, so you can see your own 500s without asking us. - An endpoint that keeps failing is switched off. After 20 failures in a row we disable it rather than keep calling a URL that is gone. A single success resets the count. Re-create the endpoint to turn it back on.
- Handle duplicates. Make your handler safe to run twice for the same message. Use the address and
receivedAtas the key.