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​