Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions openapi/developer-portal.json
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,16 @@
"type": "string"
},
"description": "RP ID (`rp_...`) is recommended. App ID (`app_...`) is also accepted for backward compatibility."
},
{
"name": "User-Agent",
"in": "header",
"required": true,
"schema": {
"type": "string",
"example": "my-world-id-backend/1.0"
},
"description": "Identifies the backend client. Requests without a User-Agent may be rejected by bot protection."
}
],
"requestBody": {
Expand Down
3 changes: 2 additions & 1 deletion world-id/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ The full code for each step is at [https://docs.world.org/world-id/idkit/integra
2. **Create or reuse the Portal resources.** Use the MCP when available. Reuse an existing app, RP, and action when they match the requested integration. For a new RP, capture `app_id`, `rp_id`, and `signing_key.private_key` from `configure_world_id`, create the action in the intended environment, and write the signing key to the prepared server-only secret store in the same step. The portal returns it exactly once. **Do not print, log, or return the private key to chat.** If the key is lost, explain that `get_world_id_signing_key` cannot recover it; rotation creates a new key and invalidates the old signer.
3. **Generate the RP signature in your backend.** *Why backend?* The signing key authenticates your app to the protocol. Leaking it lets anyone impersonate your app and forge proof requests. **CRITICAL: never sign on the client. Never expose `RP_SIGNING_KEY` as a `NEXT_PUBLIC_*` var. Never log it.**
4. **Open the IDKit widget on the client** with the signature your backend returned. The widget hands off to World ID, which produces a zero-knowledge proof.
5. **Verify the proof in your backend** by POSTing it **as-is** to `https://developer.world.org/api/v4/verify/{rp_id}`. *Why backend?* A client can return any JSON it wants. Only the World verifier — called from a trusted server — confirms the proof is real and tied to a unique credential. Verifying client-side defeats the entire point. **DO NOT mutate, re-encode, or trim the proof JSON before forwarding** — pass exactly what IDKit returned.
5. **Verify the proof in your backend** by POSTing it **as-is** to `https://developer.world.org/api/v4/verify/{rp_id}` with a descriptive `User-Agent` header (for example, `my-world-id-backend/1.0`). *Why backend?* A client can return any JSON it wants. Only the World verifier — called from a trusted server — confirms the proof is real and tied to a unique credential. Verifying client-side defeats the entire point. **DO NOT mutate, re-encode, or trim the proof JSON before forwarding** — pass exactly what IDKit returned. Requests without a `User-Agent` may be rejected by bot protection.
6. **Store the nullifier.** Every successful proof returns a `nullifier` — an RP-scoped, action-scoped, non-reversible identifier for that user. *Why store it?* Without uniqueness storage, a user can verify the same proof twice and double-claim a reward, vote, etc. Persist `(action, nullifier)` with a `UNIQUE` constraint and reject duplicates on insert. Column type: **`NUMERIC(78, 0)`** (256-bit field elements). The nullifier reveals nothing about the user — safe to store, but it's the *only* anti-replay mechanism, so it's required.

## Phase 5 — Match environments end-to-end
Expand Down Expand Up @@ -181,6 +181,7 @@ Surface these proactively when you see the matching symptom — don't make the u
|---|---|---|
| Face Check appears unresponsive or never starts | The app may not be enabled for Face Check | Read `enable_face_check` through MCP when available or `/api/v1/precheck/{app_id}`. If false, stop and explain how to request access. |
| World ID shows "action not found" or QR scan does nothing | Action wasn't created in the environment IDKit is pointing at | Create the missing action with `create_world_id_action` (`environment: "production"` for real devices, `"staging"` for simulator). Confirm `NEXT_PUBLIC_WLD_ENVIRONMENT` matches. |
| `/api/v4/verify/{rp_id}` returns a bot-protection `403` | The backend request has no `User-Agent` header | Add a descriptive `User-Agent`, such as `my-world-id-backend/1.0`, and retry. |
| `/api/v4/verify/{rp_id}` returns `invalid_proof` or `verification_failed` | Often staging/production env mismatch, or proof was mutated before forward | Re-check Phase 5. Forward the proof JSON byte-for-byte without re-encoding fields. |
| Verification fails in JS/React and the error code alone isn't enough | Need transport/payload diagnostics | Read `getDebugReport()` (or the `onError` `debugReport` arg) — it carries `transport`, `request_id`, request/response payloads, and World App `mini_app` channel info. JS SDKs only. |
| `/api/v4/verify/{rp_id}` returns `not_registered` or 4xx with `rp` errors | On-chain registration is still `pending` | Poll `get_world_id_registration_status` until the intended environment is `registered`. Production must be registered before launch. |
Expand Down
5 changes: 4 additions & 1 deletion world-id/from-idkit-standalone.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ export async function POST(request: Request): Promise<Response> {
`https://developer.world.org/api/v4/verify/${process.env.WORLD_ID_RP_ID}`,
{
method: "POST",
headers: { "content-type": "application/json" },
headers: {
"content-type": "application/json",
"user-agent": "my-world-id-backend/1.0",
},
body: JSON.stringify(idkitResponse),
},
);
Expand Down
9 changes: 7 additions & 2 deletions world-id/idkit/integrate.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@ After completion, send the returned payload to your backend and
forward it directly to `POST https://developer.world.org/api/v4/verify/{rp_id}`.

<Note>
Forward the IDKit result payload as-is. No field remapping is required.
Forward the IDKit result payload as-is. No field remapping is required. Include
a descriptive `User-Agent` header; requests without one may be rejected with a
`403` by bot protection.
</Note>

```typescript title="app/api/verify-proof/route.ts"
Expand All @@ -325,7 +327,10 @@ export async function POST(request: Request): Promise<Response> {
`https://developer.world.org/api/v4/verify/${rp_id}`,
{
method: "POST",
headers: { "content-type": "application/json" },
headers: {
"content-type": "application/json",
"user-agent": "my-world-id-backend/1.0",
},
body: JSON.stringify(idkitResponse),
},
);
Expand Down
2 changes: 1 addition & 1 deletion world-id/idkit/integration-prompt.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Integrate World ID into my project using IDKit. Here are my app details:

4. On success, send the IDKit result to my backend.
The backend should forward the payload as-is to: POST https://developer.world.org/api/v4/verify/{rp_id}
No field remapping is needed.
No field remapping is needed. Include a descriptive User-Agent header (for example, my-world-id-backend/1.0); requests without one may be rejected by bot protection.

## Reference
- Full docs: https://docs.world.org/llms.txt
Expand Down