Overview
Bulks Phone Validator verifies phone numbers at scale to ensure accuracy and validity for SMS campaigns, sales calling, and customer communication. Improve contact rates and reduce wasted outreach efforts with comprehensive phone validation.
Key Features
Number Validation : Verify phone number format, validity, and carrier information
Bulk Processing : Validate up to 2,500 phone numbers per operation
Carrier Detection : Identify mobile vs. landline numbers for appropriate outreach
Geographic Information : Determine location and timezone data for numbers
Quality Scoring : Get detailed validation status for each phone number
Export Options : Download validated numbers with detailed metadata
How Phone Validator Bulk Works
Prepare Phone List : Compile phone numbers requiring validation
Upload Numbers : Provide phone numbers via CSV file with country information
Configure Validation : Set validation parameters and quality requirements
Process Validation : Launch comprehensive phone number verification
Export Results : Download validated phone lists with quality scores
Limitations
Each Bulk is limited to 2,500 phone numbers
Special or unexpected characters may be removed from your file
Duplicate and invalid lines will not be imported
Go SDK Integration
Installation
go get github.com/tomba-io/go
Basic Setup
package main
import (
" fmt "
" log "
" time "
" encoding/csv "
" os "
" github.com/tomba-io/go/tomba "
" github.com/tomba-io/go/tomba/models "
)
func main () {
// Initialize Tomba client
client := tomba. NewTomba ( "your-api-key" , "your-secret-key" )
// Your phone validation code here
}
Creating Phone Validator Bulk from CSV
// Create phone validator bulk with column mapping
params := & models . BulkPhoneValidatorCreateParams {
BulkCreateParams: models . BulkCreateParams {
Name: "Phone Validation - Customer Database" ,
Delimiter: "," ,
},
BulkPhoneValidatorParams: models . BulkPhoneValidatorParams {
ColumnPhone: 2 , // Phone number column (1-based)
ColumnCountry: 3 , // Country code column (optional but recommended)
},
}
// Upload CSV and create validation bulk
response, err := client. CreatePhoneValidatorBulk (params, "/path/to/phone-list.csv" )
if err != nil {
log. Fatal ( "Failed to create phone validator bulk:" , err)
}
bulkID := * response.Data.ID
fmt. Printf ( "Created phone validator bulk: %d\n " , bulkID)
Alternative: Phone Numbers Only CSV
// When CSV contains only phone numbers without country codes
params := & models . BulkPhoneValidatorCreateParams {
BulkCreateParams: models . BulkCreateParams {
Name: "Phone Validation - Simple List" ,
Delimiter: "," ,
},
BulkPhoneValidatorParams: models . BulkPhoneValidatorParams {
ColumnPhone: 1 , // Phone number in first column
ColumnCountry: 0 , // No country column
},
}
response, err := client. CreatePhoneValidatorBulk (params, "simple-phone-list.csv" )
if err != nil {
log. Fatal ( "Failed to create validation bulk:" , err)
}
Launching and Monitoring Validation
// Launch the validation process
launchResponse, err := client. LaunchBulk (models.BulkTypePhoneValidator, bulkID)
if err != nil {
log. Fatal ( "Failed to launch phone validation:" , err)
}
fmt. Println ( "Phone validation started!" )
// Monitor progress with detailed status
startTime := time. Now ()
for {
progress, err := client. GetBulkProgress (models.BulkTypePhoneValidator, bulkID)
if err != nil {
log. Printf ( "Error checking progress: %v " , err)
time. Sleep ( 20 * time.Second)
continue
}
elapsed := time. Since (startTime). Round (time.Second)
fmt. Printf ( "Validation: %d%% complete ( %d processed) - %v elapsed \n " ,
progress.Progress,
progress.Processed,
elapsed,
)
if progress.Status {
fmt. Println ( "Phone validation completed!" )
break
}
// Check every 20 seconds (validation is typically faster)
time. Sleep ( 20 * time.Second)
}
Retrieving Validation Results
// Get detailed validation results
bulk, err := client. GetBulk (models.BulkTypePhoneValidator, bulkID)
if err != nil {
log. Fatal ( "Failed to get bulk details:" , err)
}
bulkInfo := bulk.Data[ 0 ]
fmt. Printf ( "Validation Summary: \n " )
fmt. Printf ( "- Campaign: %s\n " , bulkInfo.Name)
fmt. Printf ( "- Status: %v\n " , bulkInfo.Status)
fmt. Printf ( "- Numbers Processed: %d\n " , bulkInfo.Processed)
// Download all validation results
err = client. SaveBulkResults (
models.BulkTypePhoneValidator,
bulkID,
"validation-results.csv" ,
"full" ,
)
if err != nil {
log. Fatal ( "Failed to download results:" , err)
}
fmt. Println ( "Validation results saved to validation-results.csv" )
Downloading Segmented Results
// Download only valid phone numbers
err = client. SaveBulkResults (
models.BulkTypePhoneValidator,
bulkID,
"valid-phones.csv" ,
"valid" ,
)
if err != nil {
log. Printf ( "Error downloading valid phones: %v " , err)
} else {
fmt. Println ( "Valid phone numbers saved to valid-phones.csv" )
}
// Download invalid or unverifiable phone numbers
err = client. SaveBulkResults (
models.BulkTypePhoneValidator,
bulkID,
"invalid-phones.csv" ,
"not_found" ,
)
if err != nil {
log. Printf ( "Error downloading invalid phones: %v " , err)
} else {
fmt. Println ( "Invalid phone numbers saved to invalid-phones.csv" )
}
Managing Phone Validator Operations
// List all phone validator bulks
params := & models . BulkGetParams {
Page: 1 ,
Limit: 10 ,
Direction: "desc" ,
Filter: "all" ,
}
bulks, err := client. GetAllPhoneValidatorBulks (params)
if err != nil {
log. Fatal ( "Failed to get phone validator bulks:" , err)
}
fmt. Printf ( "Phone Validator Operations: \n " )
for _, bulk := range bulks.Data {
status := "In Progress"
if bulk.Status {
status = "Completed"
}
fmt. Printf ( "- %s (ID: %d ) \n " , bulk.Name, bulk.BulkID)
fmt. Printf ( " Status: %s | Progress: %d%% | Processed: %d\n " ,
status, bulk.Progress, bulk.Processed)
created := bulk.CreatedAt. Format ( "2006-01-02 15:04" )
fmt. Printf ( " Created: %s\n " , created)
fmt. Println ()
}
// Rename a validation bulk
renameParams := & models . BulkRenameParams {
Name: "Updated Phone Validation Campaign" ,
}
_, err = client. RenameBulk (models.BulkTypePhoneValidator, bulkID, renameParams)
if err != nil {
log. Fatal ( "Failed to rename bulk:" , err)
}
// Archive completed validation
_, err = client. ArchiveBulk (models.BulkTypePhoneValidator, bulkID)
if err != nil {
log. Fatal ( "Failed to archive bulk:" , err)
}
Advanced Validation Workflow with Analysis
type PhoneValidationResult struct {
OriginalNumber string
FormattedNumber string
Valid bool
LineType string // "mobile", "landline", "voip", etc.
Carrier string
Country string
CountryCode string
Region string
Timezone string
Risk string // "low", "medium", "high"
}
func validatePhoneDatabase ( client * tomba . Tomba , csvPath string , phoneCol , countryCol int ) ( * ValidationSummary , error ) {
// Step 1: Create validation bulk
params := & models . BulkPhoneValidatorCreateParams {
BulkCreateParams: models . BulkCreateParams {
Name: fmt. Sprintf ( "Phone Validation - %s " , time. Now (). Format ( "2006-01-02" )),
Delimiter: "," ,
},
BulkPhoneValidatorParams: models . BulkPhoneValidatorParams {
ColumnPhone: phoneCol,
ColumnCountry: countryCol,
},
}
response, err := client. CreatePhoneValidatorBulk (params, csvPath)
if err != nil {
return nil , fmt. Errorf ( "failed to create validation bulk: %w " , err)
}
bulkID := * response.Data.ID
log. Printf ( "Created phone validation bulk: %d " , bulkID)
// Step 2: Launch validation
_, err = client. LaunchBulk (models.BulkTypePhoneValidator, bulkID)
if err != nil {
return nil , fmt. Errorf ( "failed to launch validation: %w " , err)
}
log. Println ( "Phone validation in progress..." )
// Step 3: Monitor with progress tracking
timeout := time. After ( 30 * time.Minute)
ticker := time. NewTicker ( 15 * time.Second)
defer ticker. Stop ()
startTime := time. Now ()
for {
select {
case <- timeout:
return nil , fmt. Errorf ( "validation timed out after 30 minutes" )
case <- ticker.C:
progress, err := client. GetBulkProgress (models.BulkTypePhoneValidator, bulkID)
if err != nil {
log. Printf ( "Error checking progress: %v " , err)
continue
}
if progress.Progress % 25 == 0 || progress.Status {
elapsed := time. Since (startTime). Round (time.Second)
log. Printf ( "Validation: %d%% ( %d processed) - %v " ,
progress.Progress, progress.Processed, elapsed)
}
if progress.Status {
// Step 4: Analyze and download results
summary, err := analyzeValidationResults (client, bulkID, startTime)
if err != nil {
return nil , fmt. Errorf ( "failed to analyze results: %w " , err)
}
log. Printf ( "Phone validation completed! %d numbers processed in %v " ,
summary.TotalNumbers, summary.Duration. Round (time.Second))
return summary, nil
}
}
}
}
func analyzeValidationResults ( client * tomba . Tomba , bulkID int64 , startTime time . Time ) ( * ValidationSummary , error ) {
// Get bulk details
bulk, err := client. GetBulk (models.BulkTypePhoneValidator, bulkID)
if err != nil {
return nil , err
}
info := bulk.Data[ 0 ]
// Download and parse results
timestamp := time. Now (). Format ( "20060102-150405" )
allResultsFile := fmt. Sprintf ( "phone-validation-all- %s .csv" , timestamp)
validPhonesFile := fmt. Sprintf ( "phone-validation-valid- %s .csv" , timestamp)
// Download all results
err = client. SaveBulkResults (models.BulkTypePhoneValidator, bulkID, allResultsFile, "full" )
if err != nil {
log. Printf ( "Warning: Failed to save all results: %v " , err)
}
// Download valid results
err = client. SaveBulkResults (models.BulkTypePhoneValidator, bulkID, validPhonesFile, "valid" )
if err != nil {
log. Printf ( "Warning: Failed to save valid results: %v " , err)
}
// Parse results for analysis
results, err := parseValidationResults (allResultsFile)
if err != nil {
log. Printf ( "Warning: Could not parse results for analysis: %v " , err)
results = [] PhoneValidationResult {} // Continue with empty results
}
// Build summary
summary := & ValidationSummary {
BulkID: bulkID,
Name: info.Name,
TotalNumbers: info.Processed,
Duration: time. Since (startTime),
AllResultsFile: allResultsFile,
ValidPhonesFile: validPhonesFile,
}
// Analyze results
if len (results) > 0 {
summary.ValidNumbers = countValidNumbers (results)
summary.LineTypes = analyzeLineTypes (results)
summary.Countries = analyzeCountries (results)
summary.Carriers = analyzeCarriers (results)
}
return summary, nil
}
func parseValidationResults ( filename string ) ([] PhoneValidationResult , error ) {
file, err := os. Open (filename)
if err != nil {
return nil , err
}
defer file. Close ()
reader := csv. NewReader (file)
records, err := reader. ReadAll ()
if err != nil {
return nil , err
}
var results [] PhoneValidationResult
// Skip header row
for i := 1 ; i < len (records); i ++ {
record := records[i]
if len (record) < 3 {
continue
}
result := PhoneValidationResult {
OriginalNumber: getCSVField (record, 0 ),
Valid: getCSVField (record, 1 ) == "valid" ,
LineType: getCSVField (record, 2 ),
}
// Parse additional fields if available
if len (record) > 3 {
result.FormattedNumber = getCSVField (record, 3 )
}
if len (record) > 4 {
result.Carrier = getCSVField (record, 4 )
}
if len (record) > 5 {
result.Country = getCSVField (record, 5 )
}
if len (record) > 6 {
result.CountryCode = getCSVField (record, 6 )
}
results = append (results, result)
}
return results, nil
}
func getCSVField ( record [] string , index int ) string {
if index < len (record) {
return strings. Trim (record[index], `"` )
}
return ""
}
func countValidNumbers ( results [] PhoneValidationResult ) int {
count := 0
for _, result := range results {
if result.Valid {
count ++
}
}
return count
}
func analyzeLineTypes ( results [] PhoneValidationResult ) map [ string ] int {
types := make ( map [ string ] int )
for _, result := range results {
if result.Valid && result.LineType != "" {
types[result.LineType] ++
}
}
return types
}
func analyzeCountries ( results [] PhoneValidationResult ) map [ string ] int {
countries := make ( map [ string ] int )
for _, result := range results {
if result.Valid && result.Country != "" {
countries[result.Country] ++
}
}
return countries
}
func analyzeCarriers ( results [] PhoneValidationResult ) map [ string ] int {
carriers := make ( map [ string ] int )
for _, result := range results {
if result.Valid && result.Carrier != "" {
carriers[result.Carrier] ++
}
}
return carriers
}
// Summary structure for validation results
type ValidationSummary struct {
BulkID int64
Name string
TotalNumbers int
ValidNumbers int
Duration time . Duration
AllResultsFile string
ValidPhonesFile string
LineTypes map [ string ] int
Countries map [ string ] int
Carriers map [ string ] int
}
func ( s * ValidationSummary ) String () string {
validRate := float64 (s.ValidNumbers) / float64 (s.TotalNumbers) * 100
result := fmt. Sprintf ( `
Phone Validation Summary:
- Bulk ID: %d
- Name: %s
- Total Numbers: %d
- Valid Numbers: %d ( %.1f%% )
- Duration: %v
- All Results: %s
- Valid Only: %s ` ,
s.BulkID, s.Name, s.TotalNumbers, s.ValidNumbers, validRate,
s.Duration. Round (time.Second), s.AllResultsFile, s.ValidPhonesFile)
if len (s.LineTypes) > 0 {
result += " \n\n Line Type Distribution:"
for lineType, count := range s.LineTypes {
percent := float64 (count) / float64 (s.ValidNumbers) * 100
result += fmt. Sprintf ( " \n - %s : %d ( %.1f%% )" , lineType, count, percent)
}
}
if len (s.Countries) > 0 {
result += " \n\n Top Countries:"
// Show top 5 countries
type countryCount struct {
country string
count int
}
var sorted [] countryCount
for country, count := range s.Countries {
sorted = append (sorted, countryCount {country, count})
}
// Simple sort by count (descending)
for i := 0 ; i < len (sorted) - 1 ; i ++ {
for j := 0 ; j < len (sorted) - i - 1 ; j ++ {
if sorted[j].count < sorted[j + 1 ].count {
sorted[j], sorted[j + 1 ] = sorted[j + 1 ], sorted[j]
}
}
}
for i, cc := range sorted {
if i >= 5 { // Top 5 only
break
}
percent := float64 (cc.count) / float64 (s.ValidNumbers) * 100
result += fmt. Sprintf ( " \n - %s : %d ( %.1f%% )" , cc.country, cc.count, percent)
}
}
return result
}
// Usage example
func main () {
client := tomba. NewTomba ( "your-api-key" , "your-secret-key" )
// Validate phone database
summary, err := validatePhoneDatabase (client, "phone-database.csv" , 2 , 3 )
if err != nil {
log. Fatal ( "Phone validation failed:" , err)
}
fmt. Println (summary)
}
CSV File Format Examples
With Country Codes
name,phone,country
John Doe,+1-555-0123,US
Jane Smith,+44-20-7123-4567,GB
Pierre Martin,+33-1-23-45-67-89,FR
Maria Garcia,+34-91-123-4567,ES
Phone Numbers Only
phone
+1-555-0123
+44-20-7123-4567
+33-1-23-45-67-89
+34-91-123-4567
555-0123
With Additional Context
customer_id,name,phone,country,source
12345,John Doe,+1-555-0123,US,Website
12346,Jane Smith,+44-20-7123-4567,GB,CRM Import
12347,Pierre Martin,+33-1-23-45-67-89,FR,Lead Gen
Best Practices
Consistent Formatting : Use consistent international phone number formatting
Country Information : Include country codes or country columns for accurate validation
Regular Validation : Validate phone lists regularly to maintain quality
Campaign Optimization : Use validation results to improve calling and SMS campaigns
Compliance Checking : Verify numbers comply with local telecommunications regulations
Batch Processing : Process large lists in batches under 2,500 limit
Result Segmentation : Separate valid, invalid, and risky numbers for targeted use
Quality Thresholds : Set validation quality standards based on use case
Data Privacy : Ensure phone validation complies with privacy regulations
Cost Optimization : Track validation costs and optimize processing efficiency
Last modified on October 10, 2025