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:
- Register: Create a webhook endpoint in your Status Check account
- Subscribe: Choose which events to receive (e.g.,
validation.complete) - Validate: Status Check validates your endpoint is reachable
- Trigger validation: Start a validation batch via the API or dashboard
- Receive event: When validation completes, Status Check POSTs event data to your URL
- Verify signature: Check the
X-Signature-256header to ensure authenticity - 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:
| Event | Description | When It Fires |
|---|---|---|
validation.started | Batch validation begins | When validation processing starts for a batch of leads |
validation.complete | Batch validation finishes | When all leads in a batch are validated ✅ Most commonly used |
validation.failed | Validation encounters error | When 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
| Event | Description | When It Fires |
|---|---|---|
lead.created | New lead added | When a lead is created |
lead.updated | Lead data changes | When lead is updated |
lead.validated | Single lead validated | When individual lead validation completes |
Credit Events
| Event | Description | When It Fires |
|---|---|---|
credits.low | Credit balance low | When balance drops below 100 credits |
credits.depleted | Credits exhausted | When balance reaches 0 |
Batch Events
| Event | Description | When It Fires |
|---|---|---|
batch.started | Batch processing begins | When batch job starts |
batch.complete | Batch processing done | When batch job completes |
batch.progress | Progress update | At 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 notificationsevents: Array of events to subscribe to (subscribe tovalidation.completefor validation notifications)active: Set totrueto 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"
}
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
}
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"
}
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"})
}
- 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:
- Create a new Zap
- Choose "Webhooks by Zapier" as trigger
- Select "Catch Hook"
- Copy the webhook URL
- Create a Status Check webhook with that URL
- Test the webhook to send sample data
- Continue building your Zap workflow
Make (Integromat)
- Create a new scenario
- Add "Webhooks" → "Custom webhook" module
- Copy the webhook URL
- Create a Status Check webhook with that URL
- Run the scenario once to initialize
- Test the webhook from Status Check
- 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:
- Webhook is
active: true - Events are subscribed correctly
- Your endpoint returns
200 OK - URL is publicly accessible
- 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:
- Wrong secret: Ensure you're using the correct
whsec_...value - Body parsing: Verify signature BEFORE parsing JSON
- Encoding issues: Use UTF-8 encoding for both payload and secret
- 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:
- Slow endpoint: Takes >30s to respond → Return 200 immediately, process async
- Endpoint down: Check server status and logs
- 5xx errors: Fix application errors causing server failures
- 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