Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.myme.so/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks push events to a URL you control. Use them for integration with external systems, workflow triggers, and one-way pipelines that don’t need an always-on connection.

Registering a webhook

Register via POST /webhooks with the target URL, the event kinds to subscribe to, an optional type filter, and an HMAC secret.
POST /webhooks
{
  "url": "https://example.com/marfa/hooks",
  "events": ["item.created", "item.updated", "edge.created"],
  "type_filter": ["core.note", "core.bookmark.*"],
  "secret": "wh_abc123..."
}
FieldMeaning
urlHTTPS endpoint to POST events to. HTTP allowed for local-development targets.
eventsArray of event names. See event types. Wildcard * accepted.
type_filterOptional. Filter item events to these types only. Pattern supports * and parent.*.
secretHMAC signing key. Auto-generated if omitted. Returned only in the creation response — retrieve it then, store securely.
disabledOptional boolean. Webhook registered but inactive.

Event types

Webhooks deliver the same event surface as SSE. See the Realtime event catalog for the full list and payload shapes. Each delivery carries:
POST /your/url HTTP/1.1
Content-Type: application/json
X-Marfa-Event: item.created
X-Marfa-Event-Id: 019d9154-...
X-Marfa-Signature: t=1712345678,v1=abc123...
X-Marfa-Delivery-Id: 019d9155-...

{"event":"item.created","item":{"id":"019d...","type":"core.note",...}}

Signature verification

Every delivery is signed with HMAC-SHA256 over timestamp + "." + raw_body. Verify:
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhook(
  header: string,        // value of X-Marfa-Signature
  rawBody: string,
  secret: string,
): boolean {
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=") as [string, string])
  );
  const timestamp = parts.t;
  const received = parts.v1;
  if (!timestamp || !received) return false;

  // Reject deliveries older than 5 minutes to mitigate replay.
  const age = Math.floor(Date.now() / 1000) - Number(timestamp);
  if (age > 300 || age < -60) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  return timingSafeEqual(
    Buffer.from(received, "hex"),
    Buffer.from(expected, "hex"),
  );
}
Reject any request that fails verification. Always verify against the raw request body; JSON-parsing then re-serialising changes whitespace and breaks the signature.

Delivery semantics

  • At-least-once. A successful 2xx response is required to mark the delivery complete. Timeouts and non-2xx responses trigger retries.
  • Best-effort immediate dispatch. Initial delivery is attempted on event publication and typically lands sub-second; if the immediate attempt fails or times out, the durable retry queue takes over. Receivers see no behavioral difference between the two paths — the HMAC signature, headers, and body are byte-identical.
  • Durable retry. Deliveries persist in the server’s delivery queue across restarts. No in-memory state.
  • Exponential backoff. Default retry schedule: 30s, 2m, 10m, 1h, 6h, 24h, 72h. After the final attempt, the delivery is marked failed and surfaced via GET /webhooks/{id}/deliveries.
  • Idempotency. The X-Marfa-Delivery-Id header is unique per delivery attempt; X-Marfa-Event-Id is unique per event. Use X-Marfa-Event-Id to deduplicate across retries.
Ordering is not guaranteed. Webhooks can arrive out of order when one delivery retries and subsequent ones don’t. Apps that need ordered processing should use SSE or reconcile via event id server-side.

Managing webhooks

Standard CRUD plus delivery-history endpoints — see the Webhooks API reference for full shapes.
GET    /webhooks                        # list
GET    /webhooks/{id}                   # retrieve
PATCH  /webhooks/{id}                   # update URL, events, filters, disabled flag
DELETE /webhooks/{id}                   # remove

GET /webhooks/{id}/deliveries                        # pending + terminal
GET /webhooks/{id}/deliveries?status=failed
POST /webhooks/{id}/deliveries/{delivery_id}/retry   # force retry (admin)

Scope

Webhooks are tenant-scoped. A non-admin credential can only register and inspect webhooks in its own tenant. Admin credentials see tenant-wide webhooks.

Good practice

  • Respond 2xx immediately. Accept the event into a local queue, return 200, process asynchronously. Long-running processing during the HTTP response triggers timeouts and retries.
  • Verify signatures before trusting payload content. An unverified request body is just an arbitrary HTTP POST to your endpoint.
  • Deduplicate on X-Marfa-Event-Id. At-least-once delivery means the same event id may arrive more than once.
  • Retry on your end for transient downstream failures. If your consumer fails to process an event after signature verification, don’t return 5xx — Marfa will retry. Instead, return 200 and enqueue locally for your own retry.