Human approval for AI agents with World ID, the Workflow SDK, and the Vercel AI SDK.
The agent pauses while the human reviews an operation and authenticates with World ID. The package returns a signed approval receipt. Your application verifies that receipt before it performs the operation.
Use AI SDK 5 and Workflow SDK 4.2.0-beta.70 or a later 4.x release. Add both packages to your application:
bun add @worldcoin/human-in-the-loop @worldcoin/human-in-the-loop-reactFor Next.js, configure the Workflow SDK with withWorkflow. The flight booking example includes a complete application.
Register a confidential OpenID Connect (OIDC) client in the World IdP developer portal. Select client_secret_basic or client_secret_post as its authentication method. Enter a redirect URI when the portal requests one. This package uses device authorization, so approval completes through server polling.
Set these environment variables on the server:
WORLD_ID_CLIENT_ID=your-client-id
WORLD_ID_CLIENT_SECRET=your-client-secretThe default issuer is https://auth.world.org, and the default authentication method is client_secret_basic. Set these optional variables for a sandbox client or a different registered method:
WORLD_ID_ISSUER=https://sandbox.auth.world.org
WORLD_ID_CLIENT_AUTH_METHOD=client_secret_postKeep the client secret in server environment variables. Use the same secret across the application's workers. Changing the secret invalidates approval receipts signed with the previous secret.
Each device authorization request requires fresh World ID authentication and approval on the IdP page. The supported authentication method proves possession of an Orb v3 credential.
Register an approval tool on your DurableAgent. Define an action string that includes all inputs that affect the operation:
import { z } from 'zod'
import { requestHumanAuthorization } from '@worldcoin/human-in-the-loop/workflows'
const operationSchema = z.object({ operationId: z.string(), amount: z.number() })
type Operation = z.infer<typeof operationSchema>
const actionFor = (input: Operation) => JSON.stringify([input.operationId, input.amount])
export const approveAction = {
description: 'Request human approval for this operation.',
inputSchema: operationSchema,
execute: requestHumanAuthorization<Operation>({
action: ({ input }) => actionFor(input),
}),
}Use the same action function when you verify approval. Include a stable operation ID to distinguish separate operations with identical inputs.
The action option accepts a string or a function that receives input and the AI SDK tool-call context. Without this option, the action defaults to toolCallId.
The returned execute function runs in workflow scope. Assign it directly to the tool. The package runs network requests in steps and uses durable sleeps between polls.
Render HumanApproval for the approval tool's message part:
'use client'
import { HumanApproval, type HumanApprovalProps } from '@worldcoin/human-in-the-loop-react'
export function Approval({ message, part }: HumanApprovalProps) {
return <HumanApproval message={message} part={part} />
}The component shows the action, its inputs, and a user code. The human selects Approve with World ID or Reject. Approval opens World ID in a new tab. The human compares the user code and completes authentication. The component shows success after the server validates the result.
Use triggerLabel, successContent, and className to customize the component. For a custom interface, use useHumanApproval.
The approval tool returns a HumanApprovalResult:
interface HumanApprovalResult {
action: string
approvalId: string
receipt: string
}Pass the complete result to the tool that performs the operation. Verify it inside that tool's Workflow step, using the expected action derived from the operation inputs:
import { verifyHumanAuthorization, type HumanApprovalResult } from '@worldcoin/human-in-the-loop/workflows'
async function performOperation({ approval, ...operation }: Operation & { approval: HumanApprovalResult }) {
'use step'
const { approvalId } = await verifyHumanAuthorization(approval, actionFor(operation))
return yourService.perform(operation, { idempotencyKey: approvalId })
}Here, yourService.perform represents your application service. It must store and enforce approvalId as an idempotency key so that repeated calls perform the operation only once.
Verification checks the receipt's signature, expiry, client, action, and Workflow run. Receipts last at most five minutes and can be verified more than once during that period.
World ID verifies authentication. The package records the human's action decision and binds it to the receipt. Your application controls who can view and approve each request. Send the approval link only to the intended human.
Both helpers accept issuer, clientId, clientSecret, and clientAuthMethod options to override environment variables. Use the same configuration for approval and verification. Prefer environment variables for credentials because explicit options are stored in Workflow step arguments.
Workflow 4 keeps the decision deadline after the human responds. It can schedule an extra wake or report a pending sleep when a run ends. The integration tests check that the later wake does not repeat approval or the protected operation.
The package follows the IdP's polling interval and adds five seconds when the IdP returns slow_down. After a connection failure or timeout, it doubles the interval and polls the same attempt again. The original expiry still applies. Rejection, expiry, HTTP 429, HTTP 503, and other OAuth or validation errors end the attempt. If initiation fails, start a new approval attempt.
Workflow errors include a fixed failure code, such as initiate:http_401, token_response:http_503, signature:timeout, auth_time, or acr. These codes identify the failed stage or HTTP status. They do not include tokens, identity claims, or provider error text.
The server validates IdP tokens within a polling step and returns the approval result to the agent. Workflow stores pending device codes in its event data. Restrict access to that data and its inspection tools; encryption depends on the Workflow backend.
This release changes the server and React APIs. Upgrade both packages together.
- Register an IdP client and set
WORLD_ID_CLIENT_IDandWORLD_ID_CLIENT_SECRET. RemoveWORLD_RP_ID,WORLD_SIGNING_KEY, andNEXT_PUBLIC_WORLD_APP_IDfrom this integration. - Remove the
rpIdandsigningKeyoptions fromrequestHumanAuthorization. Keep an action that includes all operation inputs. - Replace raw
IDKitResulthandling withHumanApprovalResult. CallverifyHumanAuthorizationin the step that performs the operation. - Remove the
appId,preset, andallowLegacyProofsprops fromHumanApproval. For custom interfaces, replacerpContextandverify(proof)withverificationUri,approve(), andreject(). - Start new approval requests after deployment. Pending IDKit requests cannot complete through the IdP flow.
Use Bun and a Node.js runtime supported by your Workflow and Next.js versions. Run these commands from the repository root:
bun install
bun run checkbun run check builds both packages, checks types, and runs all tests. To run integration tests separately, first run bun run build. The release command runs the same checks before publication.
Tests use a local mock IdP and signed test tokens. Integration tests load the built package and run Workflow steps, webhooks, and sleeps. World ID and model credentials are only needed to run the flight booking example.
MIT