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

# Headless Integration

> Advanced setup for programmatic identity verification without redirects

# Headless Integration

The headless integration allows you to perform identity verification programmatically without requiring users to be redirected away from your application. This is perfect for single-page applications, mobile apps, or any scenario where you want to maintain full control over the user experience.

## Overview

Instead of redirecting users to complete OAuth flow, the headless integration:

1. **Creates an authorization request** via API
2. **Opens a popup window** for user authentication
3. **Polls for completion** while user completes verification in popup
4. **Automatically closes popup** and returns identity data
5. **Provides idv\_rec** for subsequent verification calls

## Implementation

<Note>
  You'll need your program API key from the admin dashboard to use the headless flow.
</Note>

```javascript theme={null}
class SubmitAuthorizer {
  constructor(apiKey, baseUrl = 'https://submit.hackclub.com') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
  }

  async authorize() {
    // Step 1: Create authorization request
    const response = await fetch(`${this.baseUrl}/api/authorize`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      }
    });

    if (!response.ok) {
      throw new Error('Failed to create authorization request');
    }

    const { auth_id, popup_url } = await response.json();
    
    // Step 2: Open popup window
    const popup = window.open(
      popup_url,
      'authorization',
      'width=500,height=700,scrollbars=yes,resizable=yes'
    );

    // Step 3: Poll for completion
    return new Promise((resolve, reject) => {
      const pollInterval = setInterval(async () => {
        try {
          const statusResponse = await fetch(`${this.baseUrl}/api/authorize/${auth_id}/status`, {
            headers: { 'Authorization': `Bearer ${this.apiKey}` }
          });
          
          const status = await statusResponse.json();
          
          if (status.status === 'completed') {
            clearInterval(pollInterval);
            popup.close();
            resolve({
              authId: auth_id,
              idvRec: status.idv_rec,
              completedAt: status.completed_at
            });
          }
        } catch (error) {
          clearInterval(pollInterval);
          popup.close();
          reject(error);
        }
      }, 2000); // Poll every 2 seconds

      // Handle popup closed by user
      const checkClosed = setInterval(() => {
        if (popup.closed) {
          clearInterval(checkClosed);
          clearInterval(pollInterval);
          reject(new Error('Authorization cancelled by user'));
        }
      }, 1000);
    });
  }
}
```

## Framework Examples

### React Hook

```typescript theme={null}
import { useState, useCallback } from 'react';

interface UseSubmitAuthProps {
  apiKey: string;
  baseUrl?: string;
}

interface AuthResult {
  idvRec: string;
  completedAt: string;
}

export function useSubmitAuth({ apiKey, baseUrl = 'https://submit.hackclub.com' }: UseSubmitAuthProps) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const authorize = useCallback(async (): Promise<AuthResult> => {
    setIsLoading(true);
    setError(null);

    try {
      const authorizer = new SubmitAuthorizer(apiKey, baseUrl);
      const result = await authorizer.authorize();
      return result;
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Authorization failed';
      setError(errorMessage);
      throw err;
    } finally {
      setIsLoading(false);
    }
  }, [apiKey, baseUrl]);

  return { authorize, isLoading, error };
}

// Usage in component
function VerificationForm() {
  const { authorize, isLoading, error } = useSubmitAuth({
    apiKey: process.env.REACT_APP_SUBMIT_API_KEY!
  });

  const handleVerify = async () => {
    try {
      const result = await authorize();
      // Use result.idvRec for verification
      console.log('Got idv_rec:', result.idvRec);
    } catch (error) {
      console.error('Authorization failed:', error);
    }
  };

  return (
    <button onClick={handleVerify} disabled={isLoading}>
      {isLoading ? 'Verifying...' : 'Verify Identity'}
    </button>
  );
}
```

### Vue 3 Composable

```typescript theme={null}
import { ref } from 'vue';

export function useSubmitAuth(apiKey: string, baseUrl = 'https://submit.hackclub.com') {
  const isLoading = ref(false);
  const error = ref<string | null>(null);

  const authorize = async () => {
    isLoading.value = true;
    error.value = null;

    try {
      const authorizer = new SubmitAuthorizer(apiKey, baseUrl);
      return await authorizer.authorize();
    } catch (err) {
      error.value = err instanceof Error ? err.message : 'Authorization failed';
      throw err;
    } finally {
      isLoading.value = false;
    }
  };

  return {
    authorize,
    isLoading: readonly(isLoading),
    error: readonly(error)
  };
}
```

### Next.js App Router

```typescript theme={null}
'use client';

import { useState } from 'react';

export default function VerificationPage() {
  const [result, setResult] = useState<any>(null);
  const [loading, setLoading] = useState(false);

  const handleVerify = async () => {
    setLoading(true);
    
    try {
      // Note: In production, call your API route that has the secret API key
      const response = await fetch('/api/submit/authorize', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
      });
      
      const data = await response.json();
      
      if (data.popup_url) {
        // Open popup and wait for completion
        const popup = window.open(data.popup_url, 'auth', 'width=500,height=700');
        
        // Poll for completion (implement polling logic here)
        const pollForCompletion = setInterval(async () => {
          const statusResponse = await fetch(`/api/submit/status/${data.auth_id}`);
          const status = await statusResponse.json();
          
          if (status.status === 'completed') {
            clearInterval(pollForCompletion);
            popup.close();
            setResult(status);
          }
        }, 2000);
      }
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handleVerify} disabled={loading}>
        {loading ? 'Processing...' : 'Verify Identity'}
      </button>
      {result && <p>Identity verified: {result.idv_rec}</p>}
    </div>
  );
}
```

## Security Considerations

<Warning>
  Never expose your API key in client-side code! Always use server-side routes to make API calls with your secret key.
</Warning>

### Client-Side Implementation

For client-side applications, create API routes that proxy requests:

```javascript theme={null}
// pages/api/submit/authorize.js (Next.js)
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const response = await fetch('https://submit.hackclub.com/api/authorize', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.SUBMIT_API_KEY}`,
        'Content-Type': 'application/json'
      }
    });

    const data = await response.json();
    res.status(200).json(data);
  } catch (error) {
    res.status(500).json({ error: 'Failed to create authorization request' });
  }
}
```

### Rate Limiting

The API includes rate limiting to prevent abuse:

* **Authorization requests**: 10 per minute per API key
* **Status checks**: 30 per minute per API key

Implement exponential backoff in your polling logic:

```javascript theme={null}
async function pollWithBackoff(authId, maxAttempts = 30) {
  let delay = 2000; // Start with 2 seconds
  
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const status = await checkStatus(authId);
    
    if (status.status === 'completed') {
      return status;
    }
    
    if (status.status === 'expired' || status.status === 'failed') {
      throw new Error(`Authorization ${status.status}`);
    }
    
    await new Promise(resolve => setTimeout(resolve, delay));
    delay = Math.min(delay * 1.2, 10000); // Max 10 seconds
  }
  
  throw new Error('Polling timeout');
}
```

## Troubleshooting

### Common Issues

**Popup Blocked**

* Ensure the authorization is triggered by a user action (click)
* Check browser popup settings
* Consider showing a message about allowing popups

**Authorization Timeout**

* Authorization requests expire after 15 minutes
* Implement proper error handling for expired requests
* Consider shortening your polling timeout

**API Key Issues**

* Verify your API key is correct and from the right environment
* Ensure the associated program is active
* Check that you're using Bearer token format: `Bearer pk_...`

### Debug Mode

Enable debug logging in development:

```javascript theme={null}
const authorizer = new SubmitAuthorizer(apiKey, baseUrl, { debug: true });
```

This will log all API requests and responses to help diagnose issues.

## Migration from Redirect Flow

If you're migrating from the traditional redirect-based flow:

### Before (Redirect)

```javascript theme={null}
// User clicks link -> redirected to Submit -> completes OAuth -> redirected back
window.location.href = 'https://submit.hackclub.com/your-program';
```

### After (Headless)

```javascript theme={null}
// User clicks button -> popup opens -> OAuth completes in popup -> popup closes
const result = await authorizer.authorize();
// Continue with verification using result.idvRec
```

The main benefits of the headless approach:

* **No page reload** - maintain application state
* **Better UX** - users stay on your site
* **Mobile friendly** - works well on all devices
* **Customizable** - full control over UI and error handling

## Best Practices

1. **Always handle errors gracefully** - network issues, popup blocking, etc.
2. **Implement proper loading states** - show progress during authorization
3. **Use exponential backoff** - for status polling to avoid rate limits
4. **Keep API keys secure** - never expose them client-side
5. **Test thoroughly** - across different browsers and devices
6. **Monitor usage** - track authorization success rates and failure modes

Need help? Check out our [examples repository](https://github.com/hackclub/submit-examples) or reach out in the [Hack Club Slack](https://hackclub.com/slack/).
