Skip to main content
🛠️6 Steps
⏱️~30 minutes
📚Intermediate

Prerequisites

Before you start, make sure you have:
  • Paxos Dashboard access in Sandbox environment
  • Support approval for Identity API access
  • API Key with the following scopes:
    • identity:read_identity
    • identity:read_account
    • identity:write_identity
    • identity:write_account
    • funding:read_profile
    • funding:write_profile
    • exchange:read_order
    • exchange:write_order
If you don’t have an API key yet, follow our Authentication Guide to create one. Contact Support to provision access to the Identity API.
First, authenticate with the Paxos OAuth service to get an access token:
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "strings"
)

type TokenResponse struct {
    AccessToken string `json:"access_token"`
    ExpiresIn   int    `json:"expires_in"`
    Scope       string `json:"scope"`
    TokenType   string `json:"token_type"`
}

func getAccessToken(clientID, clientSecret string) (*TokenResponse, error) {
    data := url.Values{}
    data.Set("grant_type", "client_credentials")
    data.Set("client_id", clientID)
    data.Set("client_secret", clientSecret)
    data.Set("scope", "identity:read_identity identity:read_account identity:write_identity identity:write_account funding:read_profile funding:write_profile exchange:read_order exchange:write_order")

    req, err := http.NewRequest("POST", "https://oauth.sandbox.paxos.com/oauth2/token", strings.NewReader(data.Encode()))
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    
    var tokenResp TokenResponse
    err = json.Unmarshal(body, &tokenResp)
    if err != nil {
        return nil, err
    }
    
    return &tokenResp, nil
}
Save the access_token from the response for use in subsequent requests. Access tokens expire, so you may need to refresh them periodically.
Create an Identity with passthrough verification indicating that IDV was completed by a third-party provider:
type PersonDetails struct {
    VerifierType                   string   `json:"verifier_type"`
    PassthroughVerifierType        string   `json:"passthrough_verifier_type"`
    PassthroughVerifiedAt         string   `json:"passthrough_verified_at"`
    PassthroughVerificationID     string   `json:"passthrough_verification_id"`
    PassthroughVerificationStatus string   `json:"passthrough_verification_status"`
    PassthroughVerificationFields []string `json:"passthrough_verification_fields"`
    FirstName                     string   `json:"first_name"`
    LastName                      string   `json:"last_name"`
    DateOfBirth                   string   `json:"date_of_birth"`
    Address                       Address  `json:"address"`
    Nationality                   string   `json:"nationality"`
    CipID                        string   `json:"cip_id"`
    CipIDType                    string   `json:"cip_id_type"`
    CipIDCountry                 string   `json:"cip_id_country"`
    PhoneNumber                  string   `json:"phone_number"`
    Email                        string   `json:"email"`
}

type Address struct {
    Address1 string `json:"address1"`
    City     string `json:"city"`
    Province string `json:"province"`
    Country  string `json:"country"`
    ZipCode  string `json:"zip_code"`
}

type CustomerDueDiligence struct {
    EstimatedYearlyIncome      string `json:"estimated_yearly_income"`
    ExpectedTransferValue      string `json:"expected_transfer_value"`
    SourceOfWealth            string `json:"source_of_wealth"`
    EmploymentStatus          string `json:"employment_status"`
    EmploymentIndustrySector  string `json:"employment_industry_sector"`
}

type CreateIdentityRequest struct {
    RefID                    string               `json:"ref_id"`
    PersonDetails            PersonDetails        `json:"person_details"`
    CustomerDueDiligence     CustomerDueDiligence `json:"customer_due_diligence"`
}

func createPersonIdentity(accessToken string) (map[string]interface{}, error) {
    url := "https://api.sandbox.paxos.com/v2/identity/identities"
    
    payload := CreateIdentityRequest{
        RefID: "your_end_user_ref_id",
        PersonDetails: PersonDetails{
            VerifierType:                   "PASSTHROUGH",
            PassthroughVerifierType:        "JUMIO",
            PassthroughVerifiedAt:         "2021-06-16T09:28:14Z",
            PassthroughVerificationID:     "775300ef-4edb-47b9-8ec4-f45fe3cbf41f",
            PassthroughVerificationStatus: "APPROVED",
            PassthroughVerificationFields: []string{"FULL_LEGAL_NAME", "DATE_OF_BIRTH"},
            FirstName:                     "Billy",
            LastName:                      "Duncan",
            DateOfBirth:                   "1960-01-01",
            Address: Address{
                Address1: "123 Main St",
                City:     "New York",
                Province: "NY",
                Country:  "USA",
                ZipCode:  "10001",
            },
            Nationality:  "USA",
            CipID:       "073-05-1120",
            CipIDType:   "SSN",
            CipIDCountry: "USA",
            PhoneNumber: "+1 555 678 1234",
            Email:       "example@somemail.org",
        },
        CustomerDueDiligence: CustomerDueDiligence{
            EstimatedYearlyIncome:     "INCOME_50K_TO_100K",
            ExpectedTransferValue:     "TRANSFER_VALUE_25K_TO_50K",
            SourceOfWealth:           "EMPLOYMENT_INCOME",
            EmploymentStatus:         "FULL_TIME",
            EmploymentIndustrySector: "ARTS_ENTERTAINMENT_RECREATION",
        },
    }
    
    jsonPayload, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }
    
    req, err := http.NewRequest("POST", url, strings.NewReader(string(jsonPayload)))
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Authorization", "Bearer "+accessToken)
    req.Header.Set("Content-Type", "application/json")
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var result map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&result)
    if err != nil {
        return nil, err
    }
    
    return result, nil
}
The cip_id of an Identity is required to be unique. If a 409 duplicate cip_id error occurs, handle it by either refusing to support crypto brokerage services for customers who have a duplicate cip_id, or if they are confirmed to be the same Identity, create a new Account to represent each user account.
Check the Identity status and wait for approval:
func getIdentityStatus(accessToken, identityID string) (map[string]interface{}, error) {
    url := fmt.Sprintf("https://api.sandbox.paxos.com/v2/identity/identities/%s", identityID)
    
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Authorization", "Bearer "+accessToken)
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var result map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&result)
    if err != nil {
        return nil, err
    }
    
    return result, nil
}
An Identity might stay in PENDING due to being deemed high risk by Paxos. This Identity will be required to undergo Enhanced Due Diligence. Use webhook integration to asynchronously process identity.approved and identity.denied events.
Create a Brokerage Account with Profile for the approved Identity:
type Account struct {
    IdentityID  string `json:"identity_id"`
    RefID       string `json:"ref_id"`
    Type        string `json:"type"`
    Description string `json:"description"`
}

type CreateAccountRequest struct {
    CreateProfile bool    `json:"create_profile"`
    Account       Account `json:"account"`
}

func createAccountWithProfile(accessToken, identityID string) (map[string]interface{}, error) {
    url := "https://api.sandbox.paxos.com/v2/identity/accounts"
    
    payload := CreateAccountRequest{
        CreateProfile: true,
        Account: Account{
            IdentityID:  identityID,
            RefID:       "your_account_ref",
            Type:        "BROKERAGE",
            Description: "Brokerage account for Billy Duncan",
        },
    }
    
    jsonPayload, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }
    
    req, err := http.NewRequest("POST", url, strings.NewReader(string(jsonPayload)))
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Authorization", "Bearer "+accessToken)
    req.Header.Set("Content-Type", "application/json")
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var result map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&result)
    if err != nil {
        return nil, err
    }
    
    return result, nil
}
The create_profile flag must be set to true to ensure a Profile is created for the Account. This is required for Fiat and Crypto Subledger integrations.
Create a sample buy order to test the complete flow:
type Order struct {
    Side              string `json:"side"`
    Market            string `json:"market"`
    Type              string `json:"type"`
    Price             string `json:"price"`
    BaseAmount        string `json:"base_amount"`
    IdentityID        string `json:"identity_id"`
    IdentityAccountID string `json:"identity_account_id"`
}

func createBuyOrder(accessToken, profileID, identityID, accountID string) (map[string]interface{}, error) {
    url := fmt.Sprintf("https://api.sandbox.paxos.com/v2/profiles/%s/orders", profileID)
    
    payload := Order{
        Side:              "BUY",
        Market:            "ETHUSD",
        Type:              "LIMIT",
        Price:             "1814.8",
        BaseAmount:        "1",
        IdentityID:        identityID,
        IdentityAccountID: accountID,
    }
    
    jsonPayload, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }
    
    req, err := http.NewRequest("POST", url, strings.NewReader(string(jsonPayload)))
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Authorization", "Bearer "+accessToken)
    req.Header.Set("Content-Type", "application/json")
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var result map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&result)
    if err != nil {
        return nil, err
    }
    
    return result, nil
}
In a complete integration, follow the Fiat Transfers Funding Flow guide to fund user assets. For testing in sandbox, you can use the Dashboard’s Test Funds feature to fund the account with $10,000 USD.

Step 6: Next Steps

Congratulations! You’ve successfully completed your first end-to-end third-party crypto brokerage integration with Paxos. Here’s what you can do next:
Production Considerations:
  • Implement proper error handling and retry logic
  • Set up monitoring and alerting for critical flows
  • Review security best practices for API key management
  • Test with various customer profiles and edge cases