> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Web Redirect

> Redirect users to Didit's hosted KYC page. Simple callback URLs, cross-device support, full browser compatibility. Pay-per-call from $0.30, 500 free/month.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="Redirect Integration Prompt"
  prompt={`Integrate Didit identity verification into my web app using the redirect flow.

## How It Works
1. Backend creates a session via POST /v3/session/ → response includes "url"
2. Frontend redirects user to that URL: window.location.href = url
3. User completes verification on Didit's hosted page (https://verify.didit.me/...)
4. Didit redirects user back to your callback URL with query params:
 ?verificationSessionId=<session_id>&status=<Approved|Declined|In Review>
5. Your backend receives the full, signed decision via webhook (status.updated)

## Backend: Create Session
POST https://verification.didit.me/v3/session/
Headers: { "x-api-key": DIDIT_API_KEY, "Content-Type": "application/json" }
Body: {
"workflow_id": DIDIT_WORKFLOW_ID,
"callback": "https://myapp.com/verification-complete",
"callback_method": "both",      // "initiator" | "completer" | "both"
"vendor_data": "user-id-123"
}
201 response (all 10 fields, per OpenAPI):
{
"session_id": "uuid",
"session_number": 43762,
"session_token": "3FaJ9wLqX2Mz",   // 12-char URL-safe token; valid until the session expires
"url": "https://verify.didit.me/session/3FaJ9wLqX2Mz",
"vendor_data": "user-id-123",
"metadata": null,
"status": "Not Started",
"workflow_id": "uuid",
"workflow_version": 3,
"callback": "https://myapp.com/verification-complete"
}
The field is named "url" — redirect the user there. Never build the URL from session_token
by hand (with "language" set, the URL gains a language segment: /es/session/...).

## Frontend: Redirect User
window.location.href = session.url; // value of "url" from the API response

## Callback Page: Handle Return
const params = new URLSearchParams(window.location.search);
const sessionId = params.get('verificationSessionId');
const status    = params.get('status'); // "Approved" | "Declined" | "In Review"
// IMPORTANT: Treat these query params as untrusted UI hints.
// Always confirm the final result on your backend via webhook OR by calling
// GET /v3/session/{session_id}/decision/

## Callback Methods
- "initiator": redirect only the device that started the flow
- "completer": redirect the device that finished verification (e.g. mobile after QR hand-off)
- "both": redirect whichever device is active (recommended for reliability)

## Webhook: Receive Full Results
POST to your webhook URL with full decision data.
Verify HMAC-SHA256 signature with X-Signature-V2 header — see /integration/webhooks.

## Rate Limits
600 session-create requests per minute per API key (falls back to bearer token, then client IP), plus a shared 300 write-requests/minute budget across all endpoints. HTTP 429 with Retry-After when exceeded.

## Environment Variables
- DIDIT_API_KEY — backend only (Didit Console > API & Webhooks)
- DIDIT_WEBHOOK_SECRET — backend only (per-destination, Didit Console > API & Webhooks)
- DIDIT_WORKFLOW_ID — backend only (Didit Console > Workflows)

## Docs
- Redirect flow: https://docs.didit.me/integration/web-sdks/web-redirect
- Create Session: https://docs.didit.me/sessions-api/create-session
- Retrieve Decision: https://docs.didit.me/sessions-api/retrieve-session
- Webhooks: https://docs.didit.me/integration/webhooks
`}
/>

The Redirect method sends users to a Didit-hosted page for verification, then returns them to your app via a callback URL.

| Feature               | Description                                                          |
| --------------------- | -------------------------------------------------------------------- |
| Setup Time            | 5 minutes                                                            |
| Backend Required      | ✅ Yes (to create sessions)                                           |
| Stays on Your Domain  | ❌ No (redirects to verify.didit.me or your whitelabel custom domain) |
| Cross-Device Support  | ✅ Yes                                                                |
| Browser Compatibility | ✅ All browsers                                                       |

***

## When to Use Redirect

* **Cross-device verification**: Start on desktop, complete on mobile
* **Maximum compatibility**: Works in all browsers, including older ones
* **Simple integration**: No iframe configuration needed
* **Camera issues**: When iframe camera access is problematic

***

## Implementation

### Step 1: Create a session (backend)

The session-create response uses a field named **`url`** — pass that value to the browser.

```javascript theme={null}
// Node.js / Express
app.post('/api/create-verification', async (req, res) => {
  const { userId } = req.body;

  const response = await fetch('https://verification.didit.me/v3/session/', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.DIDIT_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      workflow_id: process.env.DIDIT_WORKFLOW_ID,
      vendor_data: userId,
      callback: 'https://yourapp.com/verification-complete',
      callback_method: 'both', // most reliable across desktop ↔ mobile handoffs
    }),
  });

  if (!response.ok) {
    return res.status(response.status).send(await response.text());
  }

  const session = await response.json();
  // 201 per OpenAPI: { session_id, session_number, session_token, url, vendor_data,
  //                    metadata, status, workflow_id, workflow_version, callback }
  res.json({ sessionId: session.session_id, verificationUrl: session.url });
});
```

### Step 2: Redirect the User (Frontend)

**HTML:**

```html theme={null}
<button onclick="startVerification()">Verify Identity</button>

<script>
  async function startVerification() {
    const response = await fetch('/api/create-verification', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId: 'user-123' })
    });
    
    const { verificationUrl } = await response.json();
    window.location.href = verificationUrl;
  }
</script>
```

**React:**

```tsx theme={null}
function VerifyButton() {
  const [loading, setLoading] = useState(false);

  const startVerification = async () => {
    setLoading(true);
    
    const response = await fetch('/api/create-verification', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId: 'user-123' })
    });
    
    const { verificationUrl } = await response.json();
    window.location.assign(verificationUrl);
  };

  return (
    <button onClick={startVerification} disabled={loading}>
      {loading ? 'Loading...' : 'Verify Identity'}
    </button>
  );
}
```

**Vue:**

```vue theme={null}
<template>
  <button @click="startVerification" :disabled="loading">
    {{ loading ? 'Loading...' : 'Verify Identity' }}
  </button>
</template>

<script setup>
import { ref } from 'vue';

const loading = ref(false);

async function startVerification() {
  loading.value = true;
  
  const response = await fetch('/api/create-verification', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId: 'user-123' })
  });
  
  const { verificationUrl } = await response.json();
  window.location.href = verificationUrl;
}
</script>
```

***

## Handling the Callback

When verification completes, Didit redirects to your callback URL with query parameters:

```
https://yourapp.com/verification-complete?verificationSessionId=abc123&status=Approved
```

| Parameter               | Description                            |
| ----------------------- | -------------------------------------- |
| `verificationSessionId` | Unique session identifier              |
| `status`                | `Approved`, `Declined`, or `In Review` |

### Next.js Example

```tsx theme={null}
// pages/verification-complete.tsx
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

export default function VerificationComplete() {
  const router = useRouter();
  const { verificationSessionId, status } = router.query;
  const [message, setMessage] = useState('Processing...');

  useEffect(() => {
    if (!router.isReady) return;

    async function handleResult() {
      // Optionally verify the result with your backend
      await fetch('/api/verify-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          sessionId: verificationSessionId, 
          status 
        })
      });

      // Redirect based on status
      switch (status) {
        case 'Approved':
          setMessage('Verification successful! Redirecting...');
          setTimeout(() => router.push('/dashboard'), 2000);
          break;
        case 'Declined':
          setMessage('Verification was not successful.');
          setTimeout(() => router.push('/verification-failed'), 2000);
          break;
        case 'In Review':
          setMessage('Your verification is under review.');
          setTimeout(() => router.push('/verification-pending'), 2000);
          break;
        default:
          setMessage('Unknown status. Please contact support.');
      }
    }

    handleResult();
  }, [router.isReady, verificationSessionId, status]);

  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <div className="animate-spin w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full mb-4" />
      <p className="text-lg">{message}</p>
    </div>
  );
}
```

### Express.js Example

```javascript theme={null}
// routes/verification.js
app.get('/verification-complete', async (req, res) => {
  const { verificationSessionId, status } = req.query;
  
  // Optionally verify with Didit API
  // const session = await diditApi.getSession(verificationSessionId);
  
  // Update your database
  await db.users.update({
    where: { verificationSessionId },
    data: { verificationStatus: status }
  });
  
  // Redirect based on status
  switch (status) {
    case 'Approved':
      res.redirect('/dashboard?verified=true');
      break;
    case 'Declined':
      res.redirect('/verification-failed');
      break;
    case 'In Review':
      res.redirect('/verification-pending');
      break;
    default:
      res.redirect('/error');
  }
});
```

### Vue Router Example

```typescript theme={null}
// router/index.ts
{
  path: '/verification-complete',
  component: () => import('@/views/VerificationComplete.vue'),
  beforeEnter: async (to, from, next) => {
    const { verificationSessionId, status } = to.query;
    
    // Store result and redirect
    await store.dispatch('verification/handleResult', { 
      sessionId: verificationSessionId, 
      status 
    });
    
    if (status === 'Approved') {
      next('/dashboard');
    } else if (status === 'Declined') {
      next('/verification-failed');
    } else {
      next('/verification-pending');
    }
  }
}
```

***

## Open in New Tab

If you prefer to keep your app open while the user verifies:

```javascript theme={null}
// Open in new tab
window.open(verificationUrl, '_blank');

// Poll for completion or use webhooks
```

<Tip>
  When using new tabs, combine with [webhooks](/integration/webhooks) to know when verification completes.
</Tip>

***

## Security Best Practices

### Verify the callback server-side

Treat the `verificationSessionId` and `status` query parameters as untrusted UI hints. Always confirm the final decision on your backend — either by handling the [webhook](/integration/webhooks) (preferred) or by re-fetching the decision via the API:

```javascript theme={null}
// Backend: re-fetch the canonical decision before granting access
app.get('/verification-complete', async (req, res) => {
  const { verificationSessionId } = req.query;

  const response = await fetch(
    `https://verification.didit.me/v3/session/${verificationSessionId}/decision/`,
    {
      headers: { 'x-api-key': process.env.DIDIT_API_KEY },
    }
  );

  if (!response.ok) {
    return res.status(response.status).send(await response.text());
  }

  const decision = await response.json();

  // decision.status uses the canonical enum:
  // Not Started | In Progress | Awaiting User | In Review | Approved |
  // Declined   | Resubmitted | Expired | Kyc Expired | Abandoned
  if (decision.status === 'Approved') {
    // Grant access. Per-feature reports are plural arrays:
    //   decision.id_verifications[], decision.face_matches[], decision.aml_screenings[], ...
  }

  res.redirect('/dashboard');
});
```

### Use Webhooks

For the most reliable integration, use [webhooks](/integration/webhooks) to receive verification results directly to your backend, regardless of how the user's browser behaves.

***

## Cross-Device Flow

The redirect method naturally supports cross-device verification:

1. User clicks "Verify" on desktop
2. User is redirected to verification page
3. If needed, user can scan QR code to continue on mobile
4. After completion, the user is redirected to your callback URL — which device redirects is controlled by `callback_method` (`initiator`, `completer`, or `both`)

***

## Troubleshooting

### Callback Not Received

1. Verify the callback URL is correctly set when creating the session
2. Ensure your callback URL is accessible from the internet
3. Check that the URL doesn't have typos or encoding issues

### Status Always "Declined"

1. Check your workflow settings in the Didit Console
2. Verify the test documents you're using are valid
3. Review the session details via API for specific decline reasons

### Redirect Loop

1. Ensure your callback handler doesn't redirect back to verification
2. Check for caching issues on your callback page
3. Clear browser cache and try again

***

## Example Repository

<Card title="GitHub Repository" icon="github" href="https://github.com/didit-protocol/didit-full-demo">
  View source code and examples on GitHub
</Card>
