Journey Docs
Members APITransfers

Claim Pending Transfer

Claim pending transfers sent to your email or phone

POST/members/me/transfers/claim

Overview

Claims all pending transfers sent to the provided email or phone number. The member must own the provided contact information (verified in their account). This endpoint allows members to claim gift transfers that were sent to them before they had an account or before they verified their contact details.

Authentication

Requires a valid Clerk JWT token in the Authorization header.

Request Body

emailbodystring

Email address to claim transfers for. Must be verified in your account.

phonebodystring

Phone number to claim transfers for (E.164 format). Must be verified in your account.

Note: Either email or phone must be provided.

Request

curl -X POST "http://localhost:3000/v1/members/me/transfers/claim" \
  -H "Authorization: Bearer YOUR_CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "myemail@example.com"
  }'

Response

totalClaimednumberrequired

Total points claimed from pending transfers

messagestringrequired

Success message describing the claim result

Response

{
  "totalClaimed": 5000,
  "message": "Successfully claimed 5000 points"
}

How Claiming Works

Contact Verification Required

  • Email: Must be verified in your Clerk account
  • Phone: Must be verified in your Clerk account
  • Security: Prevents claiming transfers sent to others
  • Validation: Real-time verification against your account

Claiming Process

  1. Identify Transfers: Find all pending transfers sent to the contact
  2. Verify Ownership: Confirm you own the contact information
  3. Transfer Points: Move points from escrow to your account
  4. Update Status: Mark transfers as CLAIMED
  5. Send Notifications: Notify senders their gifts were claimed

Multiple Claims

  • Batch Processing: Claims all pending transfers at once
  • No Duplicates: Already claimed transfers are ignored
  • Atomic Operation: All transfers claimed together or none
  • Total Calculation: Sum of all successfully claimed amounts

Contact Information Requirements

Email Requirements

  • Verified: Must be verified in your Clerk account
  • Case Insensitive: Matching is case insensitive
  • Primary/Secondary: Any verified email can be used
  • Domain Matching: Exact domain matching required

Phone Requirements

  • E.164 Format: Must be in international format (+1234567890)
  • Verified: Must be verified in your Clerk account
  • Number Normalization: Numbers normalized for matching
  • Country Code: Country code required

Security Measures

Ownership Validation

  • Real-time Check: Validates contact ownership at claim time
  • Account Verification: Cross-references with Clerk verification status
  • No Spoofing: Cannot claim transfers for unverified contacts
  • Audit Trail: All claim attempts logged for security

Fraud Prevention

  • Rate Limiting: No specific rate limit (secure by design)
  • Contact Verification: Strong verification requirements
  • Transfer History: Full audit trail of claims
  • Sender Notification: Senders notified when gifts are claimed

Use Cases

New Member Onboarding

// Check for pending transfers during onboarding
const checkPendingGifts = async (userContacts: {email?: string, phone?: string}) => {
  const results = [];
  
  if (userContacts.email) {
    try {
      const emailResult = await fetch('/api/members/me/transfers/claim', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: userContacts.email })
      });
      
      const data = await emailResult.json();
      if (data.totalClaimed > 0) {
        results.push({
          contact: userContacts.email,
          type: 'email',
          claimed: data.totalClaimed
        });
      }
    } catch (error) {
      console.error('Email claim failed:', error);
    }
  }
  
  if (userContacts.phone) {
    try {
      const phoneResult = await fetch('/api/members/me/transfers/claim', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone: userContacts.phone })
      });
      
      const data = await phoneResult.json();
      if (data.totalClaimed > 0) {
        results.push({
          contact: userContacts.phone,
          type: 'phone',
          claimed: data.totalClaimed
        });
      }
    } catch (error) {
      console.error('Phone claim failed:', error);
    }
  }
  
  return results;
};

Contact Verification Trigger

// Automatically claim pending transfers when contact is verified
const onContactVerified = async (contact: string, type: 'email' | 'phone') => {
  try {
    const response = await fetch('/api/members/me/transfers/claim', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ [type]: contact })
    });
    
    const result = await response.json();
    
    if (result.totalClaimed > 0) {
      // Show success notification
      showNotification({
        title: 'Gift Points Claimed!',
        message: `You've received ${result.totalClaimed} points from pending gifts!`,
        type: 'success'
      });
      
      // Refresh wallet balance
      refreshWalletBalance();
    }
  } catch (error) {
    console.error('Auto-claim failed:', error);
  }
};

Bulk Contact Processing

// Process multiple contacts for claims
const claimAllContacts = async () => {
  const userProfile = await getUserProfile();
  const claims = [];
  
  // Process all verified emails
  for (const email of userProfile.verifiedEmails) {
    try {
      const response = await fetch('/api/members/me/transfers/claim', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email })
      });
      
      const result = await response.json();
      if (result.totalClaimed > 0) {
        claims.push({ contact: email, amount: result.totalClaimed });
      }
    } catch (error) {
      console.error(`Email claim failed for ${email}:`, error);
    }
  }
  
  // Process all verified phones
  for (const phone of userProfile.verifiedPhones) {
    try {
      const response = await fetch('/api/members/me/transfers/claim', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone })
      });
      
      const result = await response.json();
      if (result.totalClaimed > 0) {
        claims.push({ contact: phone, amount: result.totalClaimed });
      }
    } catch (error) {
      console.error(`Phone claim failed for ${phone}:`, error);
    }
  }
  
  return claims;
};

Error Handling

Common Errors

Error CodeDescriptionResolution
Missing ContactNo email or phone providedProvide either email or phone
Unverified ContactContact not verified in accountVerify contact in account settings
No TransfersNo pending transfers foundNo action needed
Claim FailedGeneric claim failureContact support

Error Recovery

const handleClaimError = (error: any, contact: string, type: 'email' | 'phone') => {
  switch (error.error) {
    case 'CLAIM_FAILED':
      if (error.message.includes('not verified')) {
        // Guide user to verify their contact
        showVerificationModal(contact, type);
      } else {
        // Generic failure - suggest retry
        showRetryOption(() => claimTransfers(contact, type));
      }
      break;
      
    default:
      // Unknown error
      showErrorMessage('Unable to claim transfers. Please try again later.');
  }
};

On this page