Skip to main content

Webhooks

Subscribe to validation events and receive real-time notifications when leads are validated.

What are Webhooks?

Webhooks allow you to subscribe to validation events and receive automatic HTTP callbacks when they occur in your Status Check account. Instead of constantly polling the API to check if a validation batch has completed, Status Check will automatically notify your application when it's done.

Use webhooks to:

  • Subscribe to validation events - Get notified when lead validation completes
  • Receive real-time updates - No need to poll the API for status
  • Build event-driven workflows - Trigger actions when validation finishes
  • Integrate with automation platforms - Connect with Zapier, Make, n8n, and more
  • Monitor credit usage - Get alerts when credits run low

Key benefit: Webhooks eliminate the need to poll the /v1/batches/{batchId} endpoint repeatedly. Instead, you register once and receive automatic notifications when validation events occur.

How Webhooks Work

┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│ Your App │ │ Status Check │ │ Your Server │
│ │ │ API │ │ (webhook) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ 1. Register Webhook │ │
│ Subscribe to events │ │
├───────────────────────>│ │
│ │ │
│ 2. Start Validation │ │
├───────────────────────>│ │
│ │ │
│ │ 3. Validation Event │
│ │ (validation.complete) │
│ ├───────────────────────>│
│ │ │
│ │ 4. Return 200 OK │
│ │<───────────────────────│
│ │ │

Webhook Lifecycle:

  1. Register: Create a webhook endpoint in your Status Check account
  2. Subscribe: Choose which events to receive (e.g., validation.complete)
  3. Validate: Status Check validates your endpoint is reachable
  4. Trigger validation: Start a validation batch via the API or dashboard
  5. Receive event: When validation completes, Status Check POSTs event data to your URL
  6. Verify signature: Check the X-Signature-256 header to ensure authenticity
  7. Process & respond: Handle the event and return a 2xx status code

No polling required! Once subscribed, you'll automatically receive notifications when validation events occur.

Available Events

Subscribe to validation events and other account activities. You can register a webhook for any combination of these events:

Validation Events

Subscribe to these events to receive notifications when lead validation occurs:

EventDescriptionWhen It Fires
validation.startedBatch validation beginsWhen validation processing starts for a batch of leads
validation.completeBatch validation finishesWhen all leads in a batch are validated ✅ Most commonly used
validation.failedValidation encounters errorWhen validation fails due to an error

Recommended workflow: Subscribe to validation.complete to receive automatic notifications when your validation batches finish processing, eliminating the need to poll the API.

Lead Events

EventDescriptionWhen It Fires
lead.createdNew lead addedWhen a lead is created
lead.updatedLead data changesWhen lead is updated
lead.validatedSingle lead validatedWhen individual lead validation completes

Credit Events

EventDescriptionWhen It Fires
credits.lowCredit balance lowWhen balance drops below 100 credits
credits.depletedCredits exhaustedWhen balance reaches 0

Batch Events

EventDescriptionWhen It Fires
batch.startedBatch processing beginsWhen batch job starts
batch.completeBatch processing doneWhen batch job completes
batch.progressProgress updateAt 25%, 50%, 75% completion

Managing Webhooks

Create a Webhook

Register a webhook and subscribe to validation events.

Endpoint: POST /v1/webhooks

Request:

curl -X POST https://api.status-check.io/v1/webhooks \
-H "X-API-Key: sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Server",
"url": "https://yourapp.com/webhooks/status-check",
"events": [
"validation.complete",
"validation.started",
"credits.low"
],
"active": true,
"description": "Receive validation completion notifications"
}'

Key fields:

  • url: Your server endpoint that will receive event notifications
  • events: Array of events to subscribe to (subscribe to validation.complete for validation notifications)
  • active: Set to true to start receiving events immediately

Response:

{
"webhook_id": "wh_abc123def456",
"user_id": "user_xyz789",
"name": "Production Server",
"url": "https://yourapp.com/webhooks/status-check",
"events": ["validation.complete", "credits.low"],
"active": true,
"secret": "whsec_3K7mP9vL2nQ8xR4wY6tF1hJ5sA0bC",
"retry_config": {
"max_retries": 3,
"retry_delays": [1000, 5000, 15000]
},
"delivery_stats": {
"total_attempts": 0,
"successful_deliveries": 0,
"failed_deliveries": 0
},
"created_at": "2026-06-16T10:30:00Z",
"updated_at": "2026-06-16T10:30:00Z"
}
Important

The secret is only shown once during creation. Store it securely to verify webhook signatures.

List Webhooks

Get all webhooks registered to your account.

Endpoint: GET /v1/webhooks

curl https://api.status-check.io/v1/webhooks \
-H "X-API-Key: sk_your_api_key"

Response:

{
"webhooks": [
{
"webhook_id": "wh_abc123",
"name": "Production Server",
"url": "https://yourapp.com/webhooks/status-check",
"events": ["validation.complete"],
"active": true,
"delivery_stats": {
"total_attempts": 150,
"successful_deliveries": 148,
"failed_deliveries": 2,
"last_delivery": "2026-06-16T10:25:00Z",
"last_success": "2026-06-16T10:25:00Z"
},
"created_at": "2026-06-01T00:00:00Z",
"updated_at": "2026-06-16T10:25:00Z"
}
],
"total": 1
}
Note

The webhook secret is never included in list responses for security.

Get Webhook Details

Retrieve a specific webhook by ID.

Endpoint: GET /v1/webhooks/{webhook_id}

curl https://api.status-check.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: sk_your_api_key"

Update Webhook

Modify webhook configuration.

Endpoint: PATCH /v1/webhooks/{webhook_id}

curl -X PATCH https://api.status-check.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"active": false,
"events": ["validation.complete", "validation.failed"]
}'

Delete Webhook

Remove a webhook endpoint.

Endpoint: DELETE /v1/webhooks/{webhook_id}

curl -X DELETE https://api.status-check.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: sk_your_api_key"

Rotate Secret

Generate a new signing secret (useful if your secret is compromised).

Endpoint: POST /v1/webhooks/{webhook_id}/rotate-secret

curl -X POST https://api.status-check.io/v1/webhooks/wh_abc123/rotate-secret \
-H "X-API-Key: sk_your_api_key"

Response:

{
"webhook_id": "wh_abc123",
"secret": "whsec_new_secret_here_abc123",
"rotated_at": "2026-06-16T10:30:00Z"
}
Security

The new secret replaces the old one immediately. Update your verification code before rotating.

Receiving Webhooks

Webhook Payload Format

All webhooks are sent as POST requests with these headers:

Content-Type: application/json
X-Webhook-Signature: sha256=abc123def456...
X-Webhook-Event: validation.complete
User-Agent: StatusCheck-Webhooks/1.0

Example Payload (validation.complete):

{
"event": "validation.complete",
"batchId": "batch_xyz789",
"userId": "user_abc123",
"status": "complete",
"totalLeads": 100,
"successfulValidations": 98,
"failedValidations": 2,
"timestamp": "2026-06-16T10:33:00Z"
}

Example Payload (credits.low):

{
"event": "credits.low",
"userId": "user_abc123",
"currentBalance": 85,
"threshold": 100,
"creditsUsed": 15,
"timestamp": "2026-06-16T10:33:00Z"
}

Verifying Webhook Signatures

Always verify webhook signatures to ensure requests are from Status Check.

Python Example

import hmac
import hashlib

def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
"""
Verify webhook signature using HMAC-SHA256

Args:
payload: Raw request body as string
signature: X-Webhook-Signature header value
secret: Your webhook secret (whsec_...)

Returns:
True if signature is valid, False otherwise
"""
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()

# Use timing-safe comparison to prevent timing attacks
return hmac.compare_digest(f"sha256={expected}", signature)

# Usage in your Flask/FastAPI endpoint
@app.post("/webhooks/status-check")
async def handle_webhook(request: Request):
# Get raw body
payload = await request.body()

# Get signature header
signature = request.headers.get("X-Webhook-Signature")

# Verify signature
if not verify_webhook_signature(
payload.decode('utf-8'),
signature,
os.getenv("STATUSCHECK_WEBHOOK_SECRET")
):
raise HTTPException(status_code=401, detail="Invalid signature")

# Parse and process event
data = await request.json()
event_type = data["event"]

if event_type == "validation.complete":
# Handle validation complete
batch_id = data["batchId"]
print(f"Validation complete for batch: {batch_id}")

return {"status": "received"}

Node.js Example

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

return signature === `sha256=${expected}`;
}

// Usage in your Express endpoint
app.post('/webhooks/status-check', express.raw({type: 'application/json'}), (req, res) => {
const payload = req.body.toString();
const signature = req.headers['x-webhook-signature'];
const secret = process.env.STATUSCHECK_WEBHOOK_SECRET;

if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature');
}

const data = JSON.parse(payload);
const eventType = data.event;

if (eventType === 'validation.complete') {
console.log(`Validation complete for batch: ${data.batchId}`);
}

res.json({ status: 'received' });
});

Go Example

package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)

func verifyWebhookSignature(payload, signature, secret string) bool {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(payload))
expected := fmt.Sprintf("sha256=%s", hex.EncodeToString(h.Sum(nil)))

return hmac.Equal([]byte(expected), []byte(signature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Read raw body
body, _ := ioutil.ReadAll(r.Body)
payload := string(body)

// Get signature
signature := r.Header.Get("X-Webhook-Signature")
secret := os.Getenv("STATUSCHECK_WEBHOOK_SECRET")

// Verify signature
if !verifyWebhookSignature(payload, signature, secret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}

// Parse event
var data map[string]interface{}
json.Unmarshal(body, &data)

eventType := data["event"].(string)

if eventType == "validation.complete" {
fmt.Printf("Validation complete for batch: %s\n", data["batchId"])
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "received"})
}
Security Best Practice
  • Always verify signatures before processing webhooks
  • Use constant-time comparison (hmac.compare_digest) to prevent timing attacks
  • Store secrets in environment variables, never in code
  • Validate event data before processing

Testing Webhooks

Test Webhook Endpoint

Send a test event to verify your endpoint is working.

Endpoint: POST /v1/webhooks/{webhook_id}/test

curl -X POST https://api.status-check.io/v1/webhooks/wh_abc123/test \
-H "X-API-Key: sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"event": "validation.complete"
}'

Response:

{
"success": true,
"response_time_ms": 145,
"event": "validation.complete",
"delivered_at": "2026-06-16T10:35:00Z"
}

Test Payload (sent to your endpoint):

{
"event": "validation.complete",
"test": true,
"message": "This is a test webhook delivery from Status Check",
"timestamp": "2026-06-16T10:35:00Z",
"data": {
"leadId": "test_lead_123",
"deliverabilityRating": 95,
"emailValid": true,
"domainHttpStatus": 200
}
}

View Delivery History

Get recent webhook delivery attempts for debugging.

Endpoint: GET /v1/webhooks/{webhook_id}/deliveries

curl https://api.status-check.io/v1/webhooks/wh_abc123/deliveries \
-H "X-API-Key: sk_your_api_key"

Response:

{
"deliveries": [
{
"delivery_id": "del_abc123",
"webhook_id": "wh_abc123",
"event": "validation.complete",
"url": "https://yourapp.com/webhooks/status-check",
"status": "success",
"http_status": 200,
"response_body": "{\"status\":\"received\"}",
"attempts": 1,
"delivered_at": "2026-06-16T10:30:00Z",
"created_at": "2026-06-16T10:30:00Z"
},
{
"delivery_id": "del_xyz789",
"webhook_id": "wh_abc123",
"event": "credits.low",
"url": "https://yourapp.com/webhooks/status-check",
"status": "failed",
"http_status": 500,
"error_message": "HTTP 500: Internal Server Error",
"attempts": 3,
"created_at": "2026-06-16T09:15:00Z"
}
],
"total": 2
}

Retry Logic

Status Check automatically retries failed webhook deliveries:

  • Max Retries: 3 attempts
  • Retry Delays: 1s, 5s, 15s (exponential backoff)
  • Timeout: 30 seconds per delivery attempt
  • Success Criteria: Any 2xx HTTP status code
  • Failure: Non-2xx status, timeout, connection error

Retry Sequence:

Attempt 1: Immediate
↓ (fails)
Attempt 2: Wait 1 second
↓ (fails)
Attempt 3: Wait 5 seconds
↓ (fails)
Attempt 4: Wait 15 seconds
↓ (final attempt)

Custom Retry Configuration

Specify custom retry behavior when creating a webhook:

{
"name": "Custom Retry Webhook",
"url": "https://yourapp.com/webhooks",
"events": ["validation.complete"],
"retry_config": {
"max_retries": 5,
"retry_delays": [1000, 2000, 5000, 10000, 30000]
}
}

Best Practices

1. Respond Quickly

Return a 200 OK response immediately, then process the event asynchronously.

❌ Don't Do This:

@app.post("/webhook")
async def handle_webhook(data: dict):
# Process immediately (blocks response)
await process_validation_results(data) # Takes 5 seconds
return {"status": "ok"} # Response delayed!

✅ Do This Instead:

from fastapi import BackgroundTasks

@app.post("/webhook")
async def handle_webhook(data: dict, background_tasks: BackgroundTasks):
# Queue for background processing
background_tasks.add_task(process_validation_results, data)

# Return immediately
return {"status": "received"} # Fast response!

2. Make Webhooks Idempotent

You may receive the same event multiple times. Handle duplicates gracefully.

processed_events = set()  # Or use Redis/database

def handle_webhook(event_data):
event_id = f"{event_data['batchId']}:{event_data['timestamp']}"

# Check if already processed
if event_id in processed_events:
logger.info(f"Duplicate event {event_id}, skipping")
return

# Process event
process_validation(event_data)

# Mark as processed
processed_events.add(event_id)

3. Validate Event Data

Don't assume webhook data is valid. Validate before processing.

from pydantic import BaseModel, ValidationError

class ValidationCompleteEvent(BaseModel):
event: str
batchId: str
totalLeads: int
successfulValidations: int

def handle_webhook(raw_data: dict):
try:
event = ValidationCompleteEvent(**raw_data)
# Process validated event
process_validation(event)
except ValidationError as e:
logger.error(f"Invalid webhook data: {e}")
return # Still return 200 to avoid retries

4. Handle Failures Gracefully

If your endpoint fails, Status Check will retry. Log errors for debugging.

@app.post("/webhook")
async def handle_webhook(request: Request):
try:
# Verify signature
if not verify_signature(request):
logger.warning("Invalid webhook signature")
return Response(status_code=401)

# Process event
data = await request.json()
await process_event(data)

return {"status": "received"}

except Exception as e:
# Log error for debugging
logger.error(f"Webhook processing error: {e}", exc_info=True)

# Return 200 if error is non-retryable (e.g., invalid data)
# Return 500 if error is temporary (e.g., database down)
return Response(status_code=500) # Will trigger retry

5. Monitor Delivery Stats

Regularly check your webhook delivery statistics:

# Check webhook health
curl https://api.status-check.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: sk_your_api_key" \
| jq '.delivery_stats'

Look for:

  • High failure rates (indicates endpoint issues)
  • Increasing failures (system degradation)
  • Recent delivery timestamps (ensure webhooks are active)

6. Use HTTPS

Always use HTTPS URLs for webhook endpoints to ensure data security.

❌ Insecure:

http://yourapp.com/webhooks

✅ Secure:

https://yourapp.com/webhooks

7. Test Before Going Live

Use the test endpoint to verify your integration:

# 1. Create webhook
curl -X POST https://api.status-check.io/v1/webhooks \
-H "X-API-Key: sk_test_..." \
-d '{"name":"Test","url":"https://yourapp.com/webhook","events":["validation.complete"]}'

# 2. Test delivery
curl -X POST https://api.status-check.io/v1/webhooks/wh_test123/test \
-H "X-API-Key: sk_test_..."

# 3. Check delivery logs
curl https://api.status-check.io/v1/webhooks/wh_test123/deliveries \
-H "X-API-Key: sk_test_..."

Platform Integration Examples

Zapier

Create a Zapier webhook trigger to receive Status Check events:

  1. Create a new Zap
  2. Choose "Webhooks by Zapier" as trigger
  3. Select "Catch Hook"
  4. Copy the webhook URL
  5. Create a Status Check webhook with that URL
  6. Test the webhook to send sample data
  7. Continue building your Zap workflow

Make (Integromat)

  1. Create a new scenario
  2. Add "Webhooks" → "Custom webhook" module
  3. Copy the webhook URL
  4. Create a Status Check webhook with that URL
  5. Run the scenario once to initialize
  6. Test the webhook from Status Check
  7. Add subsequent modules to process data

n8n

{
"name": "Status Check Webhook",
"nodes": [
{
"type": "n8n-nodes-base.webhook",
"name": "Webhook",
"webhookId": "status-check-validation",
"httpMethod": "POST"
},
{
"type": "n8n-nodes-base.function",
"name": "Process Validation",
"functionCode": "const event = $input.item.json;\nif (event.event === 'validation.complete') {\n // Process validation results\n return [event];\n}"
}
]
}

Troubleshooting

Webhook Not Receiving Events

Check:

  1. Webhook is active: true
  2. Events are subscribed correctly
  3. Your endpoint returns 200 OK
  4. URL is publicly accessible
  5. HTTPS certificate is valid

Debug:

# Check webhook status
curl https://api.status-check.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: sk_your_api_key"

# View recent deliveries
curl https://api.status-check.io/v1/webhooks/wh_abc123/deliveries \
-H "X-API-Key: sk_your_api_key"

# Send test event
curl -X POST https://api.status-check.io/v1/webhooks/wh_abc123/test \
-H "X-API-Key: sk_your_api_key"

Signature Verification Failing

Common Issues:

  1. Wrong secret: Ensure you're using the correct whsec_... value
  2. Body parsing: Verify signature BEFORE parsing JSON
  3. Encoding issues: Use UTF-8 encoding for both payload and secret
  4. Missing prefix: Signature should be sha256=...

Debug Code:

def debug_signature(payload: str, signature: str, secret: str):
# Log inputs
print(f"Payload: {payload[:100]}...")
print(f"Signature: {signature}")
print(f"Secret: {secret[:10]}...")

# Calculate expected
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()

print(f"Expected: sha256={expected}")
print(f"Received: {signature}")
print(f"Match: {hmac.compare_digest(f'sha256={expected}', signature)}")

High Failure Rate

Possible Causes:

  1. Slow endpoint: Takes >30s to respond → Return 200 immediately, process async
  2. Endpoint down: Check server status and logs
  3. 5xx errors: Fix application errors causing server failures
  4. Network issues: Verify firewall/proxy configuration

Solutions:

  • Implement queue-based processing (return 200, queue event)
  • Add health monitoring to your webhook endpoint
  • Review delivery logs to identify patterns
  • Increase server capacity if needed

Data Retention

Webhook delivery logs are retained for 30 days and automatically cleaned up.

To preserve delivery history longer than 30 days, export and store logs in your own database.

Rate Limits

Webhook deliveries are not rate limited, but your endpoint should be able to handle:

  • Burst rate: Up to 100 webhooks/second during batch completion
  • Sustained rate: Depends on your validation volume
  • Timeout: 30 seconds per delivery

If you need rate limiting, implement it on your endpoint:

from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.post("/webhook")
@limiter.limit("100/minute") # Limit to 100 webhooks per minute
async def handle_webhook(request: Request):
# Process webhook
pass

Next Steps