Skip to content

Latest commit

 

History

History
1078 lines (812 loc) · 31.6 KB

File metadata and controls

1078 lines (812 loc) · 31.6 KB

Base Verify Integration Guide

What is Base Verify?

Base Verify is for web app builders to allow their users to prove they have verified accounts (X Blue, Coinbase One) without sharing credentials.

How it works:

  1. Your app checks if user has verification → backend returns yes/no
  2. If no verification → redirect user to Base Verify web app
  3. User completes OAuth in mini app → returns to your app
  4. Check again → user now verified

Why This Matters:

Even if a wallet has few transactions, Base Verify reveals if the user is high-value through their verified social accounts (X Blue, Instagram, TikTok) or Coinbase One subscription. This lets you identify quality users regardless of on-chain activity.

Example Use Cases:

  • Token-gated airdrops or daily rewards
  • Exclusive content access (e.g. creator coins)
  • Identity-based rewards and loyalty programs

How It Works: Architecture & Flow

The Complete Flow

                    ┌─────────────┐                                                     
                                   1. User connects wallet                            
                       Your                             
                       Mini App                                                       
                                                                                      
                    └──────┬──────┘                                                     
                                                                                       
                            2. App generates SIWE message (frontend)
                                Includes wallet address
                                Includes provider (x, coinbase, instagram, tiktok)
                                Includes traits (verified:true, followers:gt:1000)
                                Includes action (base_verify_token)
                           
                            3. User signs SIWE message with wallet
                           
                            4. Send signed message to Base Verify API
                               (from frontend or via backend)
                           
                           
       
   200 OK ←───────┌──────────────────┐───────→ 400
   Verified!                                 User has account
   (DONE)           Base Verify API          However, traits not met 
                    verify.base.dev          (DONE)
                  └────────┬─────────┘
                           
                            404 Not Found
                           
                          
    5. Redirect to Base Verify Mini App
                           
                           
                    ┌──────────────────────┐
                      Base Verify           6. User completes OAuth
                      Mini App                 (X, Coinbase, Instagram, TikTok)
                      verify.base.dev       7. Base Verify stores verification
                    └──────────┬───────────┘
                               
                                8. Redirects back to your app
                               
                        ┌─────────────┐
                          Your         9. Check again (step 4)
                          Mini App         Now returns 200  or 400
                        └─────────────┘                                                    

What is SIWE and Why Do We Use It?

SIWE (Sign-In with Ethereum) is a standard way for users to prove they control a wallet address by signing a message.

Why Base Verify requires SIWE:

  1. Privacy Protection: We don't want to leak information about which wallets have verifications. By requiring a signature, only the wallet owner can check their own verification status.

  2. Security: The signature proves the request is coming from the actual wallet owner, not someone else looking up verification data.

  3. Trait Requirements: The SIWE message includes the specific traits you're checking (e.g., "X account with >1000 followers"). Base Verify validates the signature and checks if those traits match.

What goes in the SIWE message:

{
  domain: "your-app.com",
  address: "0x1234...",  // User's wallet
  chainId: 8453,         // Base
  resources: [
    "urn:verify:provider:x",                    // Which provider
    "urn:verify:provider:x:verified:eq:true",   // Trait requirements
    "urn:verify:action:base_verify_token"       // What action
  ]
}

The user signs this message with their wallet, proving they own the address and agree to check these specific traits.

The Contract: What Your App Does vs What Base Verify Does

Your App's Responsibilities:

  • Generate SIWE messages with trait requirements
  • Handle user wallet connection
  • Redirect to Base Verify Mini App when verification not found
  • Store the returned verification token to prevent reuse

Base Verify's Responsibilities:

  • Validate SIWE signatures
  • Store provider verifications (X, Coinbase, Instagram, TikTok)
  • Check if verification meets trait requirements
  • Facilitate OAuth flow with providers
  • Return deterministic tokens for Sybil resistance

Response Codes Explained

  • 200 OK: Wallet has verified the provider account AND meets all trait requirements. Returns a unique token.
  • 404 Not Found: Wallet has never verified this provider. Redirect user to Base Verify Mini App.
  • 400 Bad Request (with message "verification_traits_not_satisfied"): Wallet has verified the provider, but doesn't meet the trait requirements (e.g., has X account but not enough followers).

Key Concepts

Provider

An identity platform that Base Verify integrates with. Currently supports X (Twitter), Coinbase, Instagram, and TikTok.

Verification

Cryptographic proof that a wallet owns an account with a specific Provider.

Trait

A specific attribute of the Provider account that can be verified.

Examples:

  • verified: true - X account has blue checkmark
  • coinbase_one_active: true - Active Coinbase One subscription
  • followers: gt:1000 - X account has over 1000 followers
  • followers_count: gte:5000 - Instagram account with 5000+ followers
  • video_count: gte:50 - TikTok account with 50+ videos

Token - Sybil Resistance

A deterministic identifier tied to the Provider account, not the wallet. This is the key anti-sybil mechanism.

How it works:

A user verifies their X account with Wallet A:

  • Base Verify returns Token: abc123
  • You've never seen this token → Grant airdrop

The same user tries to claim again with Wallet B:

  • Base Verify returns Token: abc123 (same token!)
  • You've seen this token → Reject duplicate claim

Why this matters: Without Base Verify, users could claim multiple times with different wallets. With Base Verify, one verified account = one token = one claim, regardless of how many wallets they use.

Token Properties:

  • Deterministic: The same provider account always produces the same token
  • Unique per provider: A user's X token is different from their Instagram token
  • Unique per app: Your app receives different tokens than other apps (for privacy)
  • Action-specific: Tokens can vary based on the action in your SIWE message
  • Persistent: Tokens don't expire or rotate (unless the user deletes their verification)
  • Trait-independent: Token stays the same even if traits change (e.g., follower count increases)

How to Store Tokens:

// In your database
{
  token: "abc123...",           // The verification token from Base Verify
  walletAddress: "0x1234...",   // The wallet that claimed (for your records)
  provider: "x",                // Which provider was verified
  claimedAt: "2024-01-15",      // When they claimed
  // Store whatever else you need for your use case
}

Example: Preventing Double Claims

async function claimAirdrop(verificationToken: string, walletAddress: string) {
  // Check if this token was already used
  const existingClaim = await db.findClaimByToken(verificationToken);
  
  if (existingClaim) {
    return { error: "This X account already claimed" };
  }
  
  // Store the token
  await db.createClaim({
    token: verificationToken,
    wallet: walletAddress,
    claimedAt: new Date()
  });
  
  return { success: true };
}

Important: The token is the anti-sybil primitive. Even if a user connects with 100 different wallets, they'll get the same token each time because they verified with the same X/Instagram/TikTok/Coinbase account.


Quick Start: Minimal Example

Before diving into the full integration, here's the absolute minimal example showing the core flow. This example checks if a wallet has verified an X account (no trait requirements).

Step 1: Check Verification (Backend)

// Your backend endpoint
async function checkVerification(walletAddress: string, signature: string, message: string) {
  const response = await fetch('https://verify.base.dev/v1/base_verify_token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${YOUR_SECRET_KEY}`,  // Get from Base Verify team
    },
    body: JSON.stringify({
      signature: signature,
      message: message  // SIWE message signed by user
    })
  });

  if (response.status === 200) {
    const { token } = await response.json();
    return { verified: true, token };
  } else if (response.status === 404) {
    return { verified: false, needsVerification: true };
  } else if (response.status === 400) {
    const data = await response.json();
    if (data.message === 'verification_traits_not_satisfied') {
      return { verified: false, traitsNotMet: true };
    }
  }
}

Step 2: Generate SIWE Message (Frontend)

import { SiweMessage, generateNonce } from 'siwe';

// User clicks "Check Verification"
async function handleCheck() {
  // Build SIWE message
  const siweMessage = new SiweMessage({
    domain: window.location.hostname,
    address: userWalletAddress,
    statement: 'Check X verification',
    uri: window.location.origin,
    version: '1',
    chainId: 8453,
    nonce: generateNonce(),
    resources: [
      'urn:verify:provider:x',  // Checking for X provider
      'urn:verify:action:base_verify_token'
    ],
  });

  // User signs with wallet
  const message = siweMessage.prepareMessage();
  const signature = await walletSignMessage(message);

  // Send to your backend
  const result = await fetch('/api/check-verification', {
    method: 'POST',
    body: JSON.stringify({ address: userWalletAddress, signature, message })
  });

  return result.json();
}

Step 3: Redirect to Base Verify (Frontend)

// If check returns 404 (not verified)
function redirectToBaseVerify() {
  const params = new URLSearchParams({
    redirect_uri: window.location.origin,  // Where to return after verification
    providers: 'x'  // Which provider to verify
  });

  const webAppUrl = `https://verify.base.dev?${params}`;
  window.location.href = webAppUrl;
}

Step 4: Handle Return (Frontend)

// User completes verification and returns to your app
// URL will have ?success=true

if (window.location.search.includes('success=true')) {
  // Check verification again - should now return 200
  const result = await handleCheck();
  
  if (result.verified) {
    console.log('Verification successful!', result.token);
    // Store token to prevent reuse
    await saveToken(result.token);
  }
}

That's It!

This is the complete minimal flow:

  1. Check → 404
  2. Redirect to Base Verify
  3. User verifies with provider
  4. Return to your app
  5. Check again → 200

Now let's explore the full integration with configuration, traits, and error handling.


Getting Started

Prerequisites

Before integrating Base Verify, you need:

  1. API Keys - Fill out the interest form to get your keys
  2. A mini app - Your app should run in Coinbase Wallet or Base ecosystem
  3. Wallet integration - Users must be able to connect and sign messages

API Keys

You'll receive a Secret Key from the Base Verify team.

Secret Key:

  • NEVER expose to clients or frontend code
  • Must only be used on your backend server
  • Used for all API calls to Base Verify
  • Format: sec_...

Security Warning: If you accidentally expose your secret key in frontend code, browser dev tools, or version control, contact the Base Verify team immediately to rotate it.

Registering Your App

When you receive your key, you'll also need to register:

  1. Redirect URIs - Where users return after verification (e.g., https://yourapp.com)
  2. App Name - How your app appears in the Base Verify flow

Contact the Base Verify team to register these settings.


Integration Steps

Security Warning: Your Base Verify secret key must NEVER be exposed in frontend code, browser console, or version control. All Base Verify API calls must go through your backend server. If your key is compromised, contact the Base Verify team immediately.

The integration involves several components working together:

  1. SIWE Message Generation (Frontend) - Create signed messages proving wallet ownership
  2. Verification Check (Backend) - Call Base Verify API to check status
  3. Redirect Handling (Frontend) - Send users to Base Verify when needed
  4. Token Storage (Backend) - Store verification tokens to prevent reuse

Let's walk through each component:


Full Integration Example

Step 1: Set Up Configuration (Backend + Frontend)

This configuration is shared between your backend and frontend.

Create lib/config.ts:

export const config = {
  appUrl: 'https://your-app.com',
  baseVerifySecretKey: process.env.BASE_VERIFY_SECRET_KEY,  // Backend only!
  baseVerifyApiUrl: 'https://verify.base.dev/v1',
  baseVerifyWebAppUrl: 'https://verify.base.dev',
}

Create .env.local (backend):

BASE_VERIFY_SECRET_KEY=your_secret_key_here

Important: Never include your secret key in frontend code or environment variables that are exposed to the browser (like NEXT_PUBLIC_* vars).

Fill out the interest form to get your secret key.

Step 2: Create SIWE Signature Generator (Frontend)

Purpose: Generate SIWE messages that users sign with their wallet. This proves wallet ownership and includes the traits you want to verify.

Runs on: Frontend (client-side)

Create lib/signature-generator.ts:

import { SiweMessage, generateNonce } from 'siwe'
import { config } from './config'

export async function generateSignature(
  signMessageFunction: (message: string) => Promise<string>,
  address: string
) {
  // Build resources array for SIWE
  const resources = [
    'urn:verify:provider:x',
    'urn:verify:provider:x:verified:eq:true',
    'urn:verify:provider:x:followers:gte:100',
    'urn:verify:action:base_verify_token'
  ]
  
  // Create SIWE message
  const siweMessage = new SiweMessage({
    domain: new URL(config.appUrl).hostname,
    address,
    statement: 'Verify your X account',
    uri: config.appUrl,
    version: '1',
    chainId: 8453, // Base
    nonce: generateNonce(),
    issuedAt: new Date().toISOString(),
    expirationTime: new Date(Date.now() + 6 * 60 * 60 * 1000).toISOString(),
    resources,
  })
  
  const message = siweMessage.prepareMessage()
  const signature = await signMessageFunction(message)
  
  return { message, signature, address }
}

Step 3: Check Verification Status (Frontend → Backend)

Purpose: Check if the user has the required verification by calling Base Verify API.

Runs on: Backend only (secret key must not be exposed to frontend)

Flow:

  1. Frontend generates SIWE message and gets user signature
  2. Frontend sends signature to YOUR backend
  3. Your backend calls Base Verify API with secret key
  4. Your backend returns result to frontend

Frontend code:

async function checkVerification(address: string) {
  // Generate SIWE signature
  const signature = await generateSignature(
    async (msg) => {
      return new Promise((resolve, reject) => {
        signMessage(
          { message: msg },
          { onSuccess: resolve, onError: reject }
        )
      })
    },
    address
  )
  
  // Send to YOUR backend (not directly to Base Verify)
  const response = await fetch('/api/check-verification', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      signature: signature.signature,
      message: signature.message,
      address: address
    })
  })
  
  const data = await response.json();
  return data;  // Your backend returns the result
}

Backend code (YOUR API endpoint):

// pages/api/check-verification.ts
export default async function handler(req, res) {
  const { signature, message, address } = req.body;

  // Call Base Verify with YOUR secret key
  const response = await fetch('https://verify.base.dev/v1/base_verify_token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.BASE_VERIFY_SECRET_KEY}`,  // Secret key stays on backend
    },
    body: JSON.stringify({
      signature: signature,
      message: message,
    })
  });
  
  if (response.ok) {
    const data = await response.json();
    return res.status(200).json({ verified: true, token: data.token });
  } else if (response.status === 404) {
    return res.status(404).json({ verified: false, needsVerification: true });
  } else if (response.status === 400) {
    const data = await response.json();
    if (data.message === 'verification_traits_not_satisfied') {
      return res.status(400).json({ verified: false, traitsNotMet: true });
    }
  }
  
  return res.status(500).json({ error: 'Verification check failed' });
}

Step 4: Redirect to Mini App (Frontend)

Purpose: If verification not found (404), redirect user to Base Verify web app to complete OAuth.

Runs on: Frontend

function redirectToVerifyWebApp(provider: string) {
  const params = new URLSearchParams({
    redirect_uri: config.appUrl,
    providers: provider,
  })
  
  const webAppUrl = `${config.baseVerifyWebAppUrl}?${params.toString()}`
  window.location.href = webAppUrl
}

After verification, user returns to your redirect_uri with ?success=true. Check verification again to get the token.


API Reference

POST /v1/base_verify_token

Check if a wallet has a specific verification and retrieve the verification token.

Authentication: Requires Authorization: Bearer {SECRET_KEY}

Important: This endpoint must only be called from your backend. Never expose your secret key in frontend code.

Request:

{
  signature: string,   // SIWE signature from wallet
  message: string      // SIWE message (includes provider/traits in resources)
}

Example Request:

curl -X POST https://verify.base.dev/v1/base_verify_token \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -d '{
    "signature": "0x1234...",
    "message": "verify.base.dev wants you to sign in..."
  }'

Response (200 OK):

{
  token: string,        // Deterministic verification token (for Sybil resistance)
  action: string,       // Action from SIWE message
  wallet: string        // User's wallet address
}

Response (404 Not Found):

User doesn't have this verification. Redirect to mini app.

{
  error: "verification_not_found"
}

Response (400 Bad Request):

User has provider account but doesn't meet trait requirements.

{
  code: 9,
  message: "verification_traits_not_satisfied",
  details: []
}

Response (401 Unauthorized):

Invalid or missing API key.

{
  error: "unauthorized"
}

Mini App Redirect

When redirecting to Base Verify Mini App:

https://verify.base.dev?redirect_uri={your_app_url}&providers={provider}
Parameter Required Description
redirect_uri Yes Where to send user after verification
providers Yes Provider to verify: coinbase, x, instagram, or tiktok

The user completes verification in the mini app, then returns to your redirect_uri.


Trait System Reference

Traits are specific attributes of a provider account that you can verify. Before looking at provider-specific traits, understand the global trait system rules.

Trait Syntax

Traits are specified in SIWE message resources using this format:

urn:verify:provider:{provider}:{trait_name}:{operation}:{value}

Example:

urn:verify:provider:x:followers:gte:1000

This checks if an X account has greater than or equal to 1000 followers.

Operations

Operation Symbol Applies To Description Example
Equals eq All types Exact match verified:eq:true
Greater Than gt Integers Strictly greater followers:gt:1000
Greater/Equal gte Integers Greater or equal followers:gte:1000
Less Than lt Integers Strictly less followers:lt:5000
Less/Equal lte Integers Less or equal followers:lte:5000
In (list) in Strings Value in comma-separated list verified_type:in:blue,government

Type System

Boolean Traits

  • Values: "true" or "false" (as strings)
  • Only supports eq operation
  • Example: verified:eq:true

Integer Traits

  • Values: Numbers as strings
  • Supports: eq, gt, gte, lt, lte
  • Example: followers:gte:1000

String Traits

  • Values: Text strings
  • Supports: eq, in
  • Example: verified_type:eq:blue or verified_type:in:blue,government

Combining Traits

Within One Provider (AND logic):

When you specify multiple traits for the same provider, ALL must be satisfied:

resources: [
  'urn:verify:provider:x',
  'urn:verify:provider:x:verified:eq:true',
  'urn:verify:provider:x:followers:gte:10000'
]
// User must have verified X account AND 10k+ followers

Multiple Providers:

Currently, you can only check one provider per request. To check multiple providers, make separate API calls.

Code Examples

Using traits in signature generation:

// Simple boolean check
const signature = await generateSignature({
  provider: 'x',
  traits: { 'verified': 'true' },
  action: 'base_verify_token'
});

// Integer comparison
const signature = await generateSignature({
  provider: 'instagram',
  traits: { 'followers_count': 'gte:5000' },
  action: 'base_verify_token'
});

// Multiple traits (AND logic)
const signature = await generateSignature({
  provider: 'tiktok',
  traits: { 
    'follower_count': 'gte:1000',
    'likes_count': 'gte:10000',
    'video_count': 'gte:50'
  },
  action: 'base_verify_token'
});

// String with IN operation
const signature = await generateSignature({
  provider: 'coinbase',
  traits: { 
    'verified_type': 'in:blue,government'
  },
  action: 'base_verify_token'
});

Common Patterns

Tiered Access:

// Bronze tier: any verified account
traits: { 'verified': 'true' }

// Silver tier: 1k+ followers
traits: { 'followers': 'gte:1000' }

// Gold tier: 10k+ followers
traits: { 'followers': 'gte:10000' }

Content Creator Verification:

// Active TikTok creator
traits: {
  'follower_count': 'gte:5000',
  'video_count': 'gte:100',
  'likes_count': 'gte:50000'
}

Supported Providers & Traits

Each provider section below includes:

  • Trait Table: All available traits and their types
  • Code Examples: How to use traits in your integration
  • Try It Live: Interactive examples you can test with your connected wallet

About "Try It Live" Examples

The interactive examples let you test real API calls to Base Verify:

What happens when you click "Try It":

  1. Your wallet signs a SIWE message with the specified traits
  2. The request is sent to https://verify.base.dev/v1/base_verify_token
  3. You see the full request (headers, body) and response (status, data)

Response Codes:

  • 200 OK: You have verified this provider and meet the trait requirements
  • 404 Not Found: You haven't verified this provider yet (need to visit Base Verify Mini App)
  • 400 Bad Request (message: verification_traits_not_satisfied): You have the provider account but don't meet traits (e.g., not enough followers)

Why test this:

  • See exactly how the API works
  • Understand request/response formats
  • Test your own verification status
  • Debug trait requirements before implementing

Note: These examples call Base Verify API directly from your browser for demo purposes. In production, you should proxy all Base Verify API calls through your backend to keep your secret key secure.


Coinbase

Provider: coinbase

Available Traits:

Trait Type Operations Description Example Values
coinbase_one_active Boolean eq Active Coinbase One subscription "true", "false"
coinbase_one_billed Boolean eq User has been billed for Coinbase One "true", "false"

Examples:

// Check for Coinbase One subscribers
{
  provider: 'coinbase',
  traits: { 'coinbase_one_active': 'true' }
}

// Check for billed Coinbase One subscribers
{
  provider: 'coinbase',
  traits: { 'coinbase_one_billed': 'true' }
}

X (Twitter)

Provider: x

Available Traits:

Trait Type Operations Description Example Values
verified Boolean eq Has any type of verification "true", "false"
verified_type String eq Type of verification "blue", "government", "business", "none"
followers Integer eq, gt, gte, lt, lte Number of followers "1000", "50000"

Examples:

// Check for any verified account
{
  provider: 'x',
  traits: { 'verified': 'true' }
}

// Check for specific verification type
{
  provider: 'x',
  traits: { 'verified_type': 'blue' }
}

// Check for follower count (greater than or equal to)
{
  provider: 'x',
  traits: { 'followers': 'gte:1000' }
}

// Check for follower count (exact)
{
  provider: 'x',
  traits: { 'followers': 'eq:50000' }
}

// Combine multiple traits
{
  provider: 'x',
  traits: { 
    'verified': 'true',
    'followers': 'gte:10000'
  }
}

Instagram

Provider: instagram

Available Traits:

Trait Type Operations Description Example Values
username String eq Instagram username "john_doe"
followers_count Integer eq, gt, gte, lt, lte Number of followers "1000", "50000"
instagram_id String eq Unique Instagram user ID "1234567890"

Examples:

// Check for specific username
{
  provider: 'instagram',
  traits: { 'username': 'john_doe' }
}

// Check for follower count (greater than)
{
  provider: 'instagram',
  traits: { 'followers_count': 'gt:1000' }
}

// Check for follower count (greater than or equal to)
{
  provider: 'instagram',
  traits: { 'followers_count': 'gte:5000' }
}

// Combine multiple traits
{
  provider: 'instagram',
  traits: { 
    'username': 'john_doe',
    'followers_count': 'gte:10000'
  }
}

TikTok

Provider: tiktok

Available Traits:

Trait Type Operations Description Example Values
open_id String eq TikTok Open ID (unique per app) "abc123..."
union_id String eq TikTok Union ID (unique across apps) "def456..."
display_name String eq TikTok display name "John Doe"
follower_count Integer eq, gt, gte, lt, lte Number of followers "1000", "50000"
following_count Integer eq, gt, gte, lt, lte Number of accounts following "500", "2000"
likes_count Integer eq, gt, gte, lt, lte Total likes received "10000", "100000"
video_count Integer eq, gt, gte, lt, lte Number of videos posted "50", "200"

Examples:

// Check for follower count
{
  provider: 'tiktok',
  traits: { 'follower_count': 'gt:1000' }
}

// Check for likes count
{
  provider: 'tiktok',
  traits: { 'likes_count': 'gte:10000' }
}

// Check for video count
{
  provider: 'tiktok',
  traits: { 'video_count': 'gte:50' }
}

// Combine multiple traits (e.g., active creator)
{
  provider: 'tiktok',
  traits: { 
    'follower_count': 'gte:5000',
    'likes_count': 'gte:100000',
    'video_count': 'gte:100'
  }
}

// Check for specific display name
{
  provider: 'tiktok',
  traits: { 'display_name': 'John Doe' }
}

Security & Privacy

Data Storage

What Base Verify Stores:

  • Wallet addresses associated with verified provider accounts
  • Provider account metadata (username, follower counts, verification status)
  • OAuth tokens (encrypted, never shared with apps)
  • Verification timestamps

What Base Verify Does NOT Store:

  • Your users' private keys
  • Provider account passwords
  • User activity or browsing history
  • Any data beyond what's needed for verification

What Data Your App Receives

When you call /v1/base_verify_token, you receive:

Standard Response (200 OK):

{
  "token": "abc123...",      // Deterministic verification token
  "action": "base_verify_token",
  "wallet": "0x1234..."      // User's wallet address
}

No PII is returned unless you explicitly request disclosures (requires special permission):

{
  "token": "abc123...",
  "disclosures": {
    "x_username": "johndoe",           // Only if can_request_disclosures = true
    "x_followers": 5000,
    "x_verified_type": "blue"
  }
}

Privacy Protections

1. SIWE Signature Requirement

Every API call requires a valid SIWE signature from the wallet owner. This prevents:

  • Arbitrary lookup of verification status
  • Third parties checking if a wallet is verified
  • Enumeration attacks

2. OAuth Token Security

  • OAuth access tokens are encrypted at rest
  • Never exposed to your application
  • Used only by Base Verify to refresh provider data
  • Can be revoked by user at any time

3. User Control

Users can delete their verifications at any time:

  • Removes all stored provider data
  • Invalidates future token generation
  • Your app's stored tokens become meaningless (user can't re-verify with same account)

OAuth Security Model

How Base Verify validates provider accounts:

  1. User initiates OAuth in Base Verify Mini App
  2. Provider (X, Instagram, etc.) authenticates the user
  3. Provider returns OAuth token to Base Verify
  4. Base Verify fetches account data using OAuth token
  5. Base Verify stores verification linked to user's wallet
  6. OAuth token is encrypted and stored securely

Your app never handles OAuth tokens or redirects. This is all handled within the Base Verify web app.


Try Base Verify

Try the Base Verify web app: https://verify.base.dev

Try an example demo here: https://baseverifydemo.com

We'd love your feedback as you integrate! You'll be one of the first external users of Base Verify.

Get Started

Want to integrate? Fill out the interest form and we'll reach out with API access.