TombaTomba
Start free
  • Documentation
  • API reference
  • SDKs & CLI
  • AI & MCP
  • Integrations
  • Enterprise
  • Migration guides

Find, verify and enrich B2B contacts from public sources, in the app, your spreadsheet or through the API.

GDPR & CCPA

// docs

  • API changelog
  • Support options
  • Security

// platform

  • Dashboard
  • Pricing
  • API status

// resources

  • Help center
  • Product updates
  • TombaBot crawler

// company

  • Website
  • Contact us
  • Edit or remove your data

© 2021–2026 Tomba.io

  • Terms of service
  • Privacy policy
  • Cookie policy
  • Security policy
  • GDPR
  • CCPA
Get started
Platform
    Credits and usageRate limitsErrorsWebhooksVersioningAll limits
API guides
Response reference
Policies
Resources
Platform

Webhooks

Tomba can send data to your server in three ways: a callback for an individual API request when you add webhook_url, a signed event when a bulk job created with webhook_url ends, and a lead webhook that fires whenever a lead is created in your account.

Per-request callbacks

Add webhook_url to a supported request. The API still returns the result in the response as usual. When the result qualifies, Tomba also sends it to your URL.

TerminalCode
curl -G "https://api.tomba.io/v1/email-verifier" \ --data-urlencode "email=jane.doe@stripe.com" \ --data-urlencode "webhook_url=https://hooks.example.com/tomba/7f3c9a" \ -H "X-Tomba-Key: $TOMBA_API_KEY" \ -H "X-Tomba-Secret: $TOMBA_SECRET_KEY"

Supported endpoints

EndpointCallback sent when
GET /domain-searchAt least one email address is returned
GET /email-finderAn address is returned with a verification status other than unknown
GET /author-finderAn address is returned
GET /enrichAn address is returned with a verification status other than unknown
GET /linkedinAn address is returned
GET /email-verifierThe status is not disposable or unknown
GET /phone-finderA single valid phone number is returned. Not sent with full=true.
GET /people/findA person is returned
GET /combined/findA person is returned

Other endpoints ignore webhook_url.

Delivery

Tomba sends a POST request whose body is the same JSON as the API response. These headers are included:

HeaderValue
Content-Typeapplication/json
Acceptapplication/json
User-AgentTomba-Webhook/1.0
X-Tomba-TimestampUnix time, in seconds, of the delivery attempt
  • Each attempt times out after 30 seconds. A network error or a non-2xx response is retried, up to 4 attempts, 2 seconds apart. Respond with a 2xx status as soon as you've stored the payload.
  • Callbacks aren't signed. Put a hard-to-guess token in the URL, as in the example above, and reject requests that don't carry it.
  • The URL must use a public domain name; IP addresses and localhost fail with 422. Plain http:// URLs are accepted, but use https://.
  • A callback is also sent for a duplicate request, which isn't charged. Changing webhook_url makes the request a new one for the duplicate rule.

Bulk job events

Set webhook_url when you create a bulk job, and Tomba sends a signed POST request to that URL when the job ends:

typeSent when
bulk.completedThe job completed. data.metrics holds its results, and data.links.download its file.
bulk.failedThe job failed. data.error_message says why.
bulk.cancelledThe job was cancelled while running. Cancelling a pending job sends no event.

A retried job sends a new event when it ends, with data.attempt increased by one.

Code
{ "type": "bulk.completed", "created_at": "2026-09-29T09:16:19Z", "data": { "bulk_id": 4521, "bulk_type": "search", "name": "Q3 target accounts", "status": "completed", "attempt": 1, "started_at": "2026-09-29T09:14:05Z", "completed_at": "2026-09-29T09:16:18Z", "metrics": { "v": 1, "rows": { "input": 3, "valid_input": 3, "invalid_input": 0, "duplicates": 0, "skipped": 0, "processed": 3, "errors": 0 }, "results": { "found": 3, "not_found": 0, "hit_rate": 100, "records": 24 }, "distribution": {}, "emails": { "total": 24, "per_found_row": 8, "with_sources": 21, "webmail": 0, "deliverable_rate": 75 }, "errors": { "client": 0, "server": 0, "rate_limited": 0 }, "credits": { "search": 3, "verify": 24, "phone": 0, "total": 27 }, "timing": { "queued_ms": 1840, "duration_ms": 133520, "rows_per_minute": 1.3 } }, "billed_on_download": true, "links": { "api": "https://api.tomba.io/v1/bulk/search/4521", "download": "https://api.tomba.io/v1/bulk/search/4521/download", "app": "https://app.tomba.io/bulk/searches/4521" } } }
FieldDescription
type, created_atThe event, and when it was created
data.bulk_id, data.bulk_type, data.name, data.statusThe job. Phone finder jobs are phone.
data.attempt1 for the first run, then one more for each retry
data.error_messageWhy the job failed. bulk.failed only.
data.started_at, data.completed_atWhen the job started and completed
data.metricsThe job's results, as in Job fields. bulk.completed only.
data.billed_on_downloadAlways true: the job is charged when you first download its results, not when the event is sent. See Billing.
data.linksapi: the job. download: its results file, bulk.completed only. app: the job in the dashboard.

Headers

The events follow the Standard Webhooks specification:

HeaderValue
Content-Typeapplication/json
User-AgentTomba-Webhooks/1.0
webhook-idThe event's ID, such as blk_4521_1_bulk.completed. It's the same on every attempt; use it to skip duplicates.
webhook-timestampUnix time, in seconds, of the attempt
webhook-signaturev1, followed by the base64-encoded HMAC-SHA256 signature

Verify the signature

Get your secret from GET /v1/bulk/webhook-secret. It's created with your first bulk job that has a webhook_url, or by this call, and it signs the events of all your jobs. To verify an event:

  1. Remove the whsec_ prefix from the secret and base64-decode the rest: that's the key.
  2. Compute the HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body} with the key, where body is the raw request body, and base64-encode it.
  3. Compare the result with the part of webhook-signature after v1,. Reject events whose timestamp is more than 5 minutes old.

POST /v1/bulk/webhook-secret/rotate replaces the secret. The old one stops working at once, including for the events of jobs that are already running.

Delivery and retries

  • Each attempt times out after 10 seconds. A network error or a non-2xx response is retried, up to 5 attempts: the first right away, then 10 seconds, 30 seconds, 2 minutes, and 5 minutes after the previous one. A 410 Gone response stops the retries.
  • webhook_url must be an https:// URL of at most 2,048 characters, without a username or password, whose host resolves to a public IP address. Otherwise, creating the job fails with 422. Deliveries connect only to public IP addresses.
  • Each attempt is recorded in the job's history as webhook_delivered or webhook_failed, with its HTTP status; see GET /v1/bulk/{type}/{id}/history.

Lead events

The lead webhook calls your URL every time a lead is created in your account. It isn't sent when a lead is updated or deleted, and it requires a paid plan.

Set it up under Webhooks in the dashboard. An account has one lead webhook:

SettingDescription
Name2 to 50 characters.
URLWhere Tomba sends the lead. Tomba stores the URL in lowercase, including the path and query, so use a lowercase URL.
MethodPOST, PUT, PATCH, GET, or HEAD. Use POST.
HeaderOne custom header name and value, such as Authorization: Bearer <token>. Use it to authenticate Tomba's requests.
StatusWhether the webhook is enabled. New webhooks start disabled.

Tomba sends the lead with Content-Type: application/json, the User-Agent Mozilla/5.0 (compatible; TombaHooks/1.0; +https://tomba.io), and your custom header:

Code
{ "data": { "email": "jane.doe@stripe.com", "company": "Stripe", "first_name": "Jane", "last_name": "Doe", "phone": null, "website": "stripe.com", "position": "Marketing Manager", "twitter": null, "linkedin": null, "notes": null, "score": 90 } }
FieldTypeDescription
emailstringLead's email address
websitestringCompany website
companystring or nullCompany name
first_name, last_namestring or nullLead's name
phonestring or nullPhone number
positionstring or nullJob title
twitter, linkedinstring or nullSocial profiles
notesstring or nullNotes saved with the lead
scoreintegerLead score

The body has no event name, ID, or timestamp, and Tomba doesn't record whether delivery succeeded. If you need a complete record, reconcile with GET /v1/leads.

Last modified on September 30, 2026
ErrorsVersioning
On this page
  • Per-request callbacks
    • Supported endpoints
    • Delivery
  • Bulk job events
    • Headers
    • Verify the signature
    • Delivery and retries
  • Lead events
JSON
import crypto from "node:crypto"; // secret: from GET /v1/bulk/webhook-secret. body: the raw request body, as a string. export function verifyTombaEvent(secret, headers, body) { const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const signed = `${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${body}`; const expected = crypto .createHmac("sha256", key) .update(signed) .digest("base64"); const received = headers["webhook-signature"].replace(/^v1,/, ""); const age = Math.abs( Date.now() / 1000 - Number(headers["webhook-timestamp"]), ); return ( age < 300 && expected.length === received.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received)) ); }
JSON