mobieusKnow mobieusCore API — Webhooks quickstart

mobieusCore API — Webhooks quickstart

5 min read · 949 words · 5 revisions · 1 view · Updated Jun 14, 2026
#api #developers #webhooks #integration

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

  1. Register an endpoint. Admin → WebhooksAdd endpoint. Paste the HTTPS URL of your receiver, pick * (or specific event types), and click create.
  2. 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.
  3. Implement the receiver. Verify the signature, dedupe on event.id, and return any 2xx within 10 seconds.
  4. 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:

  1. Parse t and v1 out of the header.
  2. Reject if abs(now - t) > 300 seconds (replay protection).
  3. Compute v1' = HMAC_SHA256(secret, t + '.' + raw_body).
  4. Constant-time-compare v1 and v1'.

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 like 169.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.

Contributors:
S
1 view
Last edited by system · API 1.7.0 doc refresh: mobieusAI assists enumerated, paths corrected, version bumped.
Created May 28, 2026
Was this article helpful?

Discussion

Sign in to start the discussion.