Callbacks

Verify backend events safely.

Human Gateway callbacks let your backend receive signed server-to-server events after Verify or Auth flows complete.

What callbacks are

A callback is an HTTP POST request sent by Human Gateway to a backend URL configured by the partner. The callback contains event metadata, a JSON payload, timestamp headers, and a signature.

Use callbacks as your backend source of truth. Frontend SDK results are useful for immediate UX, but durable access decisions and database updates should happen only after your backend verifies a signed callback.

Supported events

Human Gateway currently verifies three signed callback event types. Verify and Auth events represent completed product flows, while callback.test is an operational delivery test.

  • verification.completed— a user completed HG Verify.
  • auth.completed— a user completed HG Auth and a partner-scoped auth session was created.
  • callback.test— a signed operational event generated from Partner Console to validate callback delivery.

Callback headers

Human Gateway sends signed metadata headers with each callback. Your backend should validate them before trusting the payload.

Callback headers

content-type: application/json
x-hg-event-id: evt_xxx
x-hg-event: verification.completed
x-hg-project-id: project_xxx
x-hg-timestamp: 1780249541000
x-hg-signature: hex_signature

verification.completed payload

This event confirms that a user completed Verify successfully for a project/action context. It does not create a Human Gateway auth session.

verification.completed

{
  "event_id": "evt_xxx",
  "event": "verification.completed",
  "success": true,
  "verified_human": true,
  "unique_for_action": true,
  "already_verified": false,
  "project_id": "project_xxx",
  "action": "hg_verify_action_key_xxx",
  "created_at": "2026-07-01T00:00:00.000Z"
}

auth.completed payload

This event confirms that a user completed Auth successfully. It includes a partner-scoped user reference and Human Gateway auth session reference.

auth.completed

{
  "event_id": "evt_xxx",
  "event": "auth.completed",
  "success": true,
  "verified_human": true,
  "project_id": "project_xxx",
  "partner_id": "partner_xxx",
  "partner_user_id": "puser_xxx",
  "auth_session_id": "authsess_xxx",
  "action": "hg_auth_action_key_xxx",
  "expires_at": "2026-08-01T00:00:00.000Z",
  "created_at": "2026-07-01T00:00:00.000Z"
}

callback.test payload

This operational event is generated when callback delivery is tested from Partner Console. It confirms transport and signature handling only; it does not represent a completed Verify or Auth flow.

callback.test

{
  "event_id": "evt_xxx",
  "event": "callback.test",
  "success": true,
  "test": true,
  "project_id": "project_xxx",
  "partner_id": "partner_xxx",
  "message": "Human Gateway callback test delivery.",
  "triggered_by": "partner_user_xxx",
  "triggered_by_role": "admin",
  "created_at": "2026-07-01T00:00:00.000Z"
}

Typed callback verification

The server SDK's verifyCallback() validates both the signed callback envelope and the required payload contract for the supported event. After narrowing on callback.event, TypeScript also narrows callback.payload to the matching event payload type.

Frontend results returned by hg.verify() or hg.auth() are separate from signed backend callback payloads. Treat frontend results as immediate UX signals and reconcile durable backend state from verified callbacks.

Signature model

Human Gateway signs callbacks with HMAC SHA-256 using the project callback secret. The signature is computed over the timestamp and the exact raw request body.

Signature formula

signed_message = "${x-hg-timestamp}.${rawBody}"
x-hg-signature = HMAC_SHA256(callback_secret, signed_message)

The callback secret is a backend secret. It must never be exposed in frontend code, public repositories, screenshots, client-side logs, or variables using a NEXT_PUBLIC_ prefix.

Use the raw body

Callback signatures must be verified against the exact raw request body. Parsing and re-stringifying JSON before verification can change formatting and cause signature validation to fail.

Raw body handling

// Correct: verify the exact raw request body.
const body = await request.text();

// Incorrect: parsing JSON before verification can break signatures.
const payload = await request.json();

Next.js App Router example

In a Next.js App Router project, create a backend route such as app/api/human-gateway/callback/route.ts. The endpoint should read the raw body, verify the Human Gateway callback, validate the expected project, and process events idempotently.

Callback endpoint

import { NextResponse } from "next/server";
import {
  HumanGatewayCallbackError,
  verifyCallback,
} from "@human-gateway/sdk/server";

export async function POST(request: Request): Promise<Response> {
  const callbackSecret = process.env.HG_CALLBACK_SECRET;
  const expectedProjectId = process.env.HG_PROJECT_ID;

  if (!callbackSecret || !expectedProjectId) {
    return NextResponse.json(
      {
        success: false,
        error: "missing_human_gateway_config",
      },
      { status: 500 }
    );
  }

  const body = await request.text();

  try {
    const callback = verifyCallback({
      body,
      headers: request.headers,
      secret: callbackSecret,
      expectedProjectId,
    });

    // Store callback.event_id and process it idempotently.
    // If this event_id was already processed, return 200
    // without repeating business logic.

    if (callback.event === "verification.completed") {
      const payload = callback.payload;

      // payload is validated and typed as verification.completed.
      // Persist verified state for payload.action.
    }

    if (callback.event === "auth.completed") {
      const payload = callback.payload;

      // payload is validated and typed as auth.completed.
      // Reconcile payload.partner_user_id and payload.auth_session_id.
    }

    if (callback.event === "callback.test") {
      const payload = callback.payload;

      // Operational test event. Do not treat it as a Verify/Auth completion.
      console.log(payload.message);
    }

    return NextResponse.json({
      success: true,
      received: true,
      event_id: callback.event_id,
      event: callback.event,
      project_id: callback.project_id,
    });
  } catch (error) {
    if (error instanceof HumanGatewayCallbackError) {
      return NextResponse.json(
        {
          success: false,
          error: error.code,
        },
        { status: 401 }
      );
    }

    return NextResponse.json(
      {
        success: false,
        error: "callback_error",
      },
      { status: 500 }
    );
  }
}

Backend environment variables

HG_PROJECT_ID=project_xxx
HG_CALLBACK_SECRET=your_callback_secret

Idempotency

Partners should treat callbacks as at-least-once delivery. The same event may be delivered more than once during retries or network uncertainty. Store event_id and make processing idempotent.

Idempotency flow

1. Receive callback.
2. Verify signature and timestamp.
3. Check whether event_id already exists.
4. If event_id exists, return 200 without repeating work.
5. If event_id is new, store it.
6. Process the business action.
7. Mark the event as processed.
8. Return 200.

Expected response

Return a successful 2xx response only after your backend accepts the callback. If the callback is invalid, return an error status and do not process the event.

A minimal successful response can be:

Successful response

{
  "success": true,
  "received": true
}

Security checklist

Callback security checklist

Security checklist
- Use HTTPS callback URLs in production
- Keep HG_CALLBACK_SECRET only on the backend
- Never expose callback secrets with NEXT_PUBLIC_
- Read the raw body before parsing JSON
- Verify x-hg-signature
- Validate x-hg-timestamp freshness
- Validate expectedProjectId
- Store event_id for idempotency
- Return 2xx only after accepting the callback
- Rotate the callback secret if it may be exposed

Privacy model

Human Gateway callbacks are designed to avoid exposing raw World ID nullifiers, raw World ID proofs, biometric data, identity documents, government identity data, or unnecessary personal identity information.

Partners may receive operational fields such as event_id, event, project_id, verified_human, partner_user_id, auth_session_id, and timestamps depending on the event.

Verification callbacks include scoped Verify fields such as verified_human, unique_for_action, already_verified, project/action context, and event metadata. Auth callbacks have their own identity and session fields.

Auth callbacks expose Human Gateway abstractions such as partner_user_id and auth_session_id. They do not expose the underlying World session identifier, proof internals, or World-specific identity references.

Next steps

Continue with the SDK guide for frontend Verify/Auth usage, SDK options, and error handling.