> ## 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.

# 웹 리다이렉트 KYC: 호스팅 인증 페이지

> Didit의 리다이렉트 방식 호스팅 신원 인증. 기기 간 인증, 콜백 URL, 전체 webhook 결과 지원. 전체 KYC 검사 건당 $0.33부터, 월 $10 무료.



리다이렉트 연동 프롬프트
        프롬프트를 클립보드에 복사



      
      

\<pre>

\`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=<session status, for example Approved, Declined, In Review or Expired>
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'); // the session status, e.g. "Approved", "Declined", "In Review" or "Expired"
// 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`

\</pre>



      
      

붙여넣을 곳

리다이렉트 방식은 사용자를 Didit이 호스팅하는 인증 페이지로 보낸 뒤, 콜백 URL을 통해 앱으로 되돌려 보냅니다.| 기능 | 설명 |
| - | - |
| 설정 시간 | 5분 |
| 백엔드 필요 여부 | ✅ 예(세션 생성 시 필요) |
| 자체 도메인 유지 | ❌ 아니요(verify.didit.me 또는 화이트 라벨 커스텀 도메인으로 리다이렉트) |
| 기기 간 지원 | ✅ 예 |
| 브라우저 호환성 | ✅ 모든 브라우저 |***## 리다이렉트를 사용할 때- **기기 간 인증**: 데스크톱에서 시작하고 모바일에서 완료합니다
- **최대 호환성**: 구형 브라우저를 포함한 모든 브라우저에서 동작합니다
- **간단한 연동**: iframe 설정이 필요하지 않습니다
- **카메라 문제**: iframe에서 카메라 접근에 문제가 있을 때***## 구현### 1단계: 세션 생성(백엔드)세션 생성 응답은 **`url`**이라는 필드를 사용합니다. 이 값을 브라우저에 전달하세요.```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 });
});
```### 2단계: 사용자 리다이렉트(프런트엔드)**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>
```***## 콜백 처리인증이 완료되면 Didit은 다음과 같은 쿼리 파라미터를 붙여 콜백 URL로 리다이렉트합니다:```
https://yourapp.com/verification-complete?verificationSessionId=abc123&status=Approved
```| 파라미터 | 설명 |
| - | - |
| `verificationSessionId` | 고유한 세션 식별자 |
| `status` | 세션의 현재 상태입니다. 예를 들어 `Approved`, `Declined`, `In Review` 또는 `Expired`가 있습니다. [인증 상태](https://docs.didit.me/ko/integration/verification-statuses.md)를 참고하세요 |### Next.js 예제```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 예제```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 예제```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');
    }
  }
}
```***## 새 탭에서 열기앱을 열어 둔 채로 사용자가 인증하게 하려면 다음과 같이 합니다:```javascript theme={null}
// Open in new tab
window.open(verificationUrl, '_blank');

// Poll for completion or use webhooks
```> [!TIP]
>
> 새 탭을 사용할 때는 [webhook](https://docs.didit.me/ko/integration/webhooks.md)과 함께 사용하면 인증이 언제 완료되는지 알 수 있습니다.***## 보안 권장 사항### 콜백을 서버 측에서 검증하세요`verificationSessionId` 및 `status` 쿼리 파라미터는 신뢰할 수 없는 UI 힌트로 취급하세요. 최종 결정은 항상 백엔드에서 확인해야 합니다. [webhook](https://docs.didit.me/ko/integration/webhooks.md)을 처리하는 방법(권장)이나 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');
});
```### webhook 사용가장 안정적인 연동을 위해서는 사용자의 브라우저 동작과 관계없이 인증 결과를 백엔드로 직접 받는 [webhook](https://docs.didit.me/ko/integration/webhooks.md)을 사용하세요.***## 기기 간 흐름리다이렉트 방식은 기기 간 인증을 자연스럽게 지원합니다:1) 사용자가 데스크톱에서 "Verify"를 클릭합니다
2) 사용자가 인증 페이지로 리다이렉트됩니다
3) 필요한 경우 사용자는 QR 코드를 스캔해 모바일에서 계속할 수 있습니다
4) 완료 후 사용자는 콜백 URL로 리다이렉트되며, 어느 기기에서 리다이렉트를 수행할지는 `callback_method`(`initiator`, `completer`, `both`)로 제어됩니다***## 문제 해결### 콜백이 수신되지 않음1) 세션을 생성할 때 콜백 URL이 올바르게 설정되었는지 확인하세요
2) 콜백 URL에 인터넷에서 접근할 수 있는지 확인하세요
3) URL에 오타나 인코딩 문제가 없는지 확인하세요### 상태가 항상 "Declined"로 표시됨1) Didit Console에서 워크플로 설정을 확인하세요
2) 사용 중인 테스트 문서가 유효한지 확인하세요
3) API로 세션 세부 정보를 조회해 구체적인 거부 사유를 확인하세요### 리다이렉트 루프1) 콜백 핸들러가 다시 인증 페이지로 리다이렉트하지 않는지 확인하세요
2) 콜백 페이지에 캐싱 문제가 있는지 확인하세요
3) 브라우저 캐시를 지우고 다시 시도하세요***## 예제 저장소> [card] [GitHub 저장소](https://github.com/didit-protocol/didit-full-demo)
>
> GitHub에서 소스 코드와 예제를 확인하세요


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.