Ship a receiver that survives production.

A complete, implementation-ready contract for raw-body verification, destination ownership, durable deduplication, ordered delivery, bounded retries, and governed replay—plus copy-ready TypeScript, Python, Java, and Go receivers.

4versioned topics

Exact control-plane facts, each with an explicit aggregate boundary.

4receiver languages

Raw-byte HMAC examples with constant-time comparison.

5 minreplay window

Reject stale delivery timestamps before business work.

0provider payload claims

The guide states exactly what its evidence can support.

Console events—not product webhooks.

These four topics describe this developer console reference implementation. They are not Mappls product webhooks, InTouch payloads, or the 138 application-owned event blueprints.

Verify first. Commit once. Acknowledge last.

The secure path is deliberately short. Each step closes one failure mode without assuming exactly-once transport.

  1. 01

    Bound

    Reject a body above 256 KiB and retain the exact raw bytes.

  2. 02

    Verify

    Validate timestamp, event ID, signature shape, and constant-time HMAC before parsing.

  3. 03

    Parse

    Decode JSON and allow-list the exact versioned topic and event schema.

  4. 04

    Deduplicate

    Insert the event ID into a unique inbox record in the same transaction as the business effect.

  5. 05

    Commit

    Persist the normalized effect, evidence, and any application outbox work atomically.

  6. 06

    Acknowledge

    Return 2xx only after commit; otherwise return a deliberate non-2xx for bounded retry.

Production receiver core
import { createHmac, timingSafeEqual } from "node:crypto";

const MAX_AGE_SECONDS = 300;

export function verifyMapplsWebhook(input: {
  rawBody: Buffer;
  headers: Record<string, string | undefined>;
  secret: string;
  nowSeconds?: number;
}) {
  const timestamp = input.headers["x-mappls-timestamp"] ?? "";
  const eventId = input.headers["x-mappls-event-id"] ?? "";
  const signature = input.headers["x-mappls-signature"] ?? "";
  const now = input.nowSeconds ?? Math.floor(Date.now() / 1000);

  if (!/^\d{10}$/.test(timestamp) || Math.abs(now - Number(timestamp)) > MAX_AGE_SECONDS) {
    throw new Error("stale_webhook");
  }
  if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/.test(eventId)) {
    throw new Error("invalid_event_id");
  }
  if (!/^v1=[a-f0-9]{64}$/.test(signature)) throw new Error("invalid_signature");

  const expected = createHmac("sha256", input.secret)
    .update(`${timestamp}.${eventId}.`)
    .update(input.rawBody)
    .digest();
  const actual = Buffer.from(signature.slice(3), "hex");
  if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) {
    throw new Error("invalid_signature");
  }
  return JSON.parse(input.rawBody.toString("utf8"));
}

export function verificationResponse(secret: string, challengeId: string, nonce: string) {
  return `v1=${createHmac("sha256", secret)
    .update(`${challengeId}.${nonce}`)
    .digest("hex")}`;
}

// Commit event.id and your durable effect in one transaction.
// Return 2xx only after that transaction commits.

Authenticate the exact bytes you received.

Do not parse, reserialize, trim, or otherwise normalize the body before computing the digest.

Signed bytes

${timestamp}.${eventId}.${rawBody}

HMAC-SHA256 · signature v1=<hex> · raw body limit 256 KiB

  • Read and bound the raw request bytes before JSON parsing.
  • Reject timestamps outside the five-minute window before doing business work.
  • Recompute the HMAC over the exact timestamp, event ID, separators, and raw body.
  • Compare digests in constant time and reject unknown signature versions.
  • Keep endpoint signing secrets in a receiver-side secret manager; never log or return them.

Delivery headers

content-type
application/json
x-mappls-event-id
Stable delivery identity and receiver deduplication key
x-mappls-event-type
Versioned topic identity
x-mappls-timestamp
Unix seconds included in the signature
x-mappls-signature
v1=<lowercase HMAC-SHA256 hex>
x-mappls-test-mode
true only for console-generated synthetic tests

Prove the receiver before opening business delivery.

A configured endpoint receives a signed webhook.endpoint_verification.v1 challenge. Return x-mappls-verification-response: v1=<lowercase HMAC-SHA256 hex> within 15 minutes.

1Configure endpoint2Receive signed challenge3Return challenge proof4Activate delivery
A 2xx with the exact proof establishes destination ownership for that challenge version; it does not prove business-event handling or provider entitlement.

Make failure observable and recovery deliberate.

Retry

Network errors, timeouts, and non-2xx responses use bounded Retry-After or exponential backoff until terminal exhaustion.

Ordering

The worker dispatches only the earliest unresolved delivery in each endpoint + aggregate type + aggregate ID lane. Independent lanes continue in parallel.

Deduplication

Create a unique durable inbox record for every event ID in the same transaction as the business effect.

Governed replay

Replay preserves the original event ID and aggregate sequence; it is another attempt at the same transition. Never bypass receiver deduplication for a replay. Reconciliation and replay approval are operational evidence, not a new event.

Four versioned control-plane topics.

Subscribe only to topics your receiver can validate and handle idempotently.

application.provisioning_requested.v1

Application provisioning requested

The control plane durably accepted a provisioning request and queued the provider handoff. The application is not active yet.

Ordering aggregate · application
application.provisioned.v1

Application provisioned

A separately authenticated provider result activated the requested application identity. Consumers should still read current entitlement and credential state.

Ordering aggregate · application
usage.metered.v1

Usage metered

An authoritative meter event was accepted for one application operation. Use it as a notification, not as a replacement for billing reconciliation.

Ordering aggregate · application
usage.threshold_reached.v1

Usage threshold reached

A configured monthly usage alert crossed its threshold for one product and application. Re-read current usage before taking consequential action.

Ordering aggregate · usage_alert

Every transition leaves evidence.

  1. 1
    configured

    Endpoint and exact topic set recorded; signing secret revealed once.

  2. 2
    verification pending

    Signed ownership challenge queued; business delivery remains closed.

  3. 3
    verified + active

    Synthetic tests and subscribed business fan-out may be admitted.

  4. 4
    queued

    Immutable event identity and aggregate lane allocated transactionally.

  5. 5
    delivering

    One fenced worker lease owns the bounded attempt.

  6. 6
    delivered / retrying / failed

    Safe attempt evidence records success, retry schedule, or terminal exhaustion.

  7. 7
    replay governed

    Paused-only request, independent decision, single-use authorization, and preserved identity.

Do not stop at a successful curl.

Ship only after the receiver, its secret boundary, failure paths, operational evidence, and recovery runbook have all been exercised.

Review trust boundaries
  • Public HTTPS receiver on standard port 443 with no redirect dependency
  • Raw-body capture and 256 KiB limit before parsing
  • Five-minute timestamp rejection and constant-time HMAC comparison
  • Endpoint-specific signing secret stored in a managed secret system
  • Unique durable inbox constraint on event ID
  • Inbox record, domain effect, audit, and application outbox committed atomically
  • Versioned schema allow-list and unknown-topic rejection
  • Fast 2xx after commit; slow work moved to an application queue
  • Synthetic challenge and subscribed-topic tests completed
  • Duplicate, stale, malformed, timeout, 429, 5xx, ordering, and replay tests
  • Metrics for latency, signature rejection, duplicates, retries, lane blocking, and terminal failures
  • Runbook for pause, drain, secret rotation, reconciliation, approved replay, and rollback