Skip to main content

Build integrations · Partner

SAPP webhooks

Receive signed account-change events and verify the HMAC signature before reconciling with the read API.

SAPP webhooks notify subscribed applications when tenants, users, memberships, permissions, or metadata change. Each delivery is an HTTP POST with a signed JSON body.

HTTP requestSend this request from a trusted backend and replace the host, identifiers, and credentials with your ONE values.
POST /sapp/webhooks HTTP/1.1
Host: receiver.example.net
Content-Type: application/json
User-Agent: Skyfallen-SAPP/1.0
X-Skyfallen-SAPP-Protocol: SAPP
X-Skyfallen-SAPP-Delivery: 018fd4ca-07f9-75d3-a3f2-b1ff7ce2b8b2
X-Skyfallen-SAPP-Event: users.metadata.update
X-Skyfallen-SAPP-Timestamp: 1780675200
X-Skyfallen-SAPP-Signature: v1=0f45b87cfbc9cdb0f0f2fdf9922abdf872655e0f0acff5d63d7139d950e59f52

{
  "id": "018fd4ca-07f9-75d3-a3f2-b1ff7ce2b8b2",
  "protocol": "SAPP",
  "protocol_version": "2026-06-05",
  "event": "users.metadata.update",
  "occurred_at": "2026-06-05T10:32:20.000000Z",
  "tenant_ids": [
    "018fd4c2-57de-7bc4-8e53-4e5758c0f624"
  ],
  "resource": {
    "type": "user",
    "id": "018fd4c0-e2da-7c7d-8b8e-0f033193b383"
  },
  "context": {},
  "changes": {
    "metadata": {
      "hris_id": {
        "old": null,
        "new": "HR-100044"
      }
    }
  }
}

Subscription model

Subscriptions are managed in ONE administration and stored as SAPP subscriptions.

Field Meaning
app_name Receiving application name.
publisher Publisher or owner of the receiving application.
tenant_id Tenant-specific subscription. null means all matching events.
webhook_url HTTPS endpoint that receives webhook posts.
secret HMAC signing secret.
subscription_fields Event scopes selected by the receiving app.
enabled Disabled subscriptions do not receive webhooks.

Event scopes

Tenant-scoped subscriptions receive only matching tenant events. User events are delivered to tenant-scoped subscriptions for every tenant the user belongs to.

Scope Emitted when
tenants.create A tenant is created.
tenants.update A tenant changes.
tenants.metadata.update Tenant metadata changes.
users.create A user is created.
users.update A user changes.
users.metadata.update User metadata changes.
memberships.update A membership, membership role, or membership permission changes.

Delivery headers

SAPP sends these headers on every webhook delivery:

Header Meaning
User-Agent Skyfallen-SAPP/1.0.
X-Skyfallen-SAPP-Protocol Always SAPP.
X-Skyfallen-SAPP-Delivery Delivery ID, same as payload id.
X-Skyfallen-SAPP-Event Event name.
X-Skyfallen-SAPP-Timestamp Unix timestamp used in the signature base string.
X-Skyfallen-SAPP-Signature Versioned HMAC signature.

Signature verification

The signature format is:

Use the raw request body exactly as received. Do not parse and re-serialize JSON before verifying.

Reject timestamps outside your replay window. Five minutes is a practical default.

v1={hex_hmac_sha256(secret, timestamp + "." + raw_request_body)}
import crypto from "node:crypto";

function verifySapp(secret, timestamp, rawBody, signature) {
  const now = Math.floor(Date.now() / 1000);
  const ts = Number(timestamp);

  if (!Number.isFinite(ts) || Math.abs(now - ts) > 300) {
    return false;
  }

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

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
function verify_sapp(string $secret, int $timestamp, string $rawBody, string $signature): bool
{
    if (abs(time() - $timestamp) > 300) {
        return false;
    }

    $expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);

    return hash_equals($expected, $signature);
}

Payload shape

All webhook payloads share the same base shape.

JSONRepresentative JSON response; production objects can include more fields depending on the endpoint and privileges.
{
  "id": "018fd4c8-e77f-74be-97ab-88f8fb10b9d7",
  "protocol": "SAPP",
  "protocol_version": "2026-06-05",
  "event": "users.update",
  "occurred_at": "2026-06-05T10:30:00.000000Z",
  "tenant_ids": [
    "018fd4c2-57de-7bc4-8e53-4e5758c0f624"
  ],
  "resource": {
    "type": "user",
    "id": "018fd4c0-e2da-7c7d-8b8e-0f033193b383"
  },
  "context": {},
  "changes": {
    "status": {
      "old": "email_not_verified",
      "new": "active"
    }
  }
}

Delivery behavior

Webhook delivery is queued. The job sends each HTTP request with a 10 second timeout and retries transient outbound attempts inside the job.

Non-2xx responses are logged and treated as failed deliveries by the queue worker.

Consumers should treat webhooks as change notifications. If the full current object matters, call the SAPP read API after accepting and verifying the event.

English