mobieusCore API — Webhooks quickstart
Webhooks quickstart
Webhooks push community events to your own HTTPS endpoint as they happen.
You skip polling GET /api/v1/events and react in near-real-time. Same envelope
shape, same event types, same data payloads — see the events reference.
Five-minute setup
- Register an endpoint. Admin → Webhooks → Add endpoint. Paste the
HTTPS URL of your receiver, pick
*(or specific event types), and click create. - Copy the signing secret. It's shown on the success page, exactly once. Mobieus only stores a SHA-256 hash — if you lose it, you create a new endpoint.
- Implement the receiver. Verify the signature, dedupe on
event.id, and return any 2xx within 10 seconds. - Send a test event. From the endpoint detail page, click Send test event.
Within ~60 seconds you'll see a delivery row appear with status
succeeded.
The signed request
Every webhook delivery looks like this:
POST /your-receiver HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: Mobieus-Webhook/1.0
Mobieus-Signature: t=1716902400,v1=4f8a0b7c8d2f3e6...
Mobieus-Event-Id: evt_01JG5K8HW2X4A8Q3M1T6KQ7BWP
Mobieus-Event-Type: post.created
{
"id": "evt_01JG5K8HW2X4A8Q3M1T6KQ7BWP",
"type": "post.created",
"created_at": "2026-05-28T12:34:56.123456Z",
"data": {
"post_id": 9182,
"thread_id": 1124,
"forum_slug": "general",
"title": "Welcome to the community",
"author": { "id": 42, "username": "jordan" }
}
}
Verifying the signature
The Mobieus-Signature header is t=<unix>,v1=<hex>. To verify:
- Parse
tandv1out of the header. - Reject if
abs(now - t) > 300seconds (replay protection). - Compute
v1' = HMAC_SHA256(secret, t + '.' + raw_body). - Constant-time-compare
v1andv1'.
Node.js
const crypto = require('crypto');
const SECRET = process.env.MOBIEUS_WEBHOOK_SECRET;
const TOLERANCE_S = 300;
function verifyMobieusSignature(header, rawBody) {
if (!header) return false;
const parts = Object.fromEntries(header.split(',').map(p => p.split('=', 2)));
const t = parseInt(parts.t, 10);
const v1 = parts.v1;
if (!t || !v1) return false;
if (Math.abs(Math.floor(Date.now()/1000) - t) > TOLERANCE_S) return false;
const expected = crypto.createHmac('sha256', SECRET)
.update(`${t}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(v1, 'hex')
);
}
// Express:
const express = require('express');
const app = express();
app.post('/mobieus',
express.raw({ type: 'application/json' }),
(req, res) => {
const ok = verifyMobieusSignature(
req.headers['mobieus-signature'],
req.body.toString('utf8')
);
if (!ok) return res.status(400).end();
const evt = JSON.parse(req.body);
// Idempotent: skip if you've seen evt.id before.
handle(evt);
res.status(200).end();
}
);
Python
import hmac, hashlib, time
SECRET = os.environ['MOBIEUS_WEBHOOK_SECRET'].encode()
TOLERANCE = 300
def verify_mobieus_signature(header: str, raw_body: bytes) -> bool:
if not header:
return False
parts = dict(p.split('=', 1) for p in header.split(','))
try:
t = int(parts['t'])
v1 = parts['v1']
except (KeyError, ValueError):
return False
if abs(int(time.time()) - t) > TOLERANCE:
return False
expected = hmac.new(
SECRET,
f'{t}.'.encode() + raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, v1)
# Flask:
from flask import Flask, request
app = Flask(__name__)
@app.post('/mobieus')
def receive():
if not verify_mobieus_signature(
request.headers.get('Mobieus-Signature', ''),
request.get_data()
):
return ('', 400)
evt = request.get_json()
# Idempotent: skip if you've seen evt['id'] before.
handle(evt)
return ('', 200)
Delivery semantics
| Aspect | Behavior |
|---|---|
| Guarantee | At-least-once. Receivers MUST be idempotent on event.id. |
| Ordering | Best-effort. post.created for the same post may arrive AFTER post.edited if the first delivery had to retry. |
| Per-attempt timeout | 10 seconds (configurable per tenant: webhooks.per_attempt_timeout_seconds). |
| Retry schedule | 0s → 1m → 5m → 30m → 2h → 6h → 24h, then status=dead. (Configurable: webhooks.retry_schedule_minutes.) |
| Auto-disable | After 20 consecutive failed deliveries the endpoint is auto-disabled and audit-logged. (Configurable: webhooks.auto_disable_after_failures.) Re-enable in the admin UI. |
What counts as success
Any 2xx HTTP response within the timeout window. Any non-2xx, network error, TLS error, or timeout is treated as a transient failure and retried.
Your receiver should return 200 as soon as it's accepted the event into a local queue — do the heavy work asynchronously. Returning 200 after 9 seconds of in-line processing wastes our timeout budget for no reason.
SSRF protection
Mobieus refuses to call any URL that isn't:
https://(no plain HTTP)- Resolving to a public IP (no
127.0.0.1, no RFC1918, no link-local, no cloud-metadata addresses like169.254.169.254)
This check runs at registration AND again before every delivery, so DNS rebinding tricks can't pivot a previously-allowed hostname onto an internal IP.
Debugging
Every delivery row in /admin/webhooks/{id} shows:
- HTTP status code from your receiver
- Total request latency in ms
- The first 2,000 chars of your response body (for surfacing your own error messages)
- The last error string (cURL or timeout reason)
- Attempt N of M, next-attempt time
Click Send test event to fire a synthetic webhook.test event without
waiting for real traffic. The receiver sees it exactly like a normal delivery.
New webhook events (1.6.0)
Nine new events are now available in addition to the original community core events:
mobieusHelp
| Event | When |
|---|---|
ticket.created |
A new support ticket is opened |
ticket.replied |
An agent or requester adds a message |
ticket.status_changed |
Status changes (open, pending, resolved, closed) |
ticket.assigned |
Ticket is assigned to an agent or team |
mobieusLearn
| Event | When |
|---|---|
enrollment.created |
A learner enrolls in a course |
enrollment.completed |
A learner completes a course |
course.published |
A course is published for the first time |
mobieusKnow
| Event | When |
|---|---|
page.created |
A new mobieusKnow page is created and approved |
page.updated |
An approved edit is applied to a page |
Subscribe to these events the same way as any other — from Admin → Webhooks, pick the event types you want, or use ["*"] to receive all.
Discussion
Sign in to start the discussion.