Webhooks
Webhooks let you receive HTTP POST callbacks the moment a validation event occurs, with no polling required. MailCop sends a signed JSON payload to your endpoint.
Managing Webhooks
Open Webhooks from the sidebar to create, edit, or delete webhook endpoints.
Creating a Webhook
- Click Add Webhook
- Enter a Name (for your reference), URL (your HTTPS endpoint), and select the Events to subscribe to
- Copy the full Secret Token shown in the banner immediately after creation. It is shown only once; afterwards only a masked version is displayed.
Regenerating a Secret Token
Open the webhook, click Regenerate next to the secret token. The new full token is shown once on the next screen; copy it then. All subsequent deliveries use the new token.
Deleting a Webhook
Click Delete on the webhook list. Delivery stops immediately.
Events
| Event | Fires when |
|---|---|
api.validation_completed |
A single-email API call to /v1/verify completes |
email_list.started |
A bulk list upload begins processing |
email_list.completed |
A bulk list finishes validation |
email_list.failed |
A bulk list fails (e.g. malformed CSV) |
Example Payloads
api.validation_completed
{
"event_type": "api.validation_completed",
"timestamp": "2026-01-01T12:00:00Z",
"data": {
"email": "[email protected]",
"validation_type": "mx",
"result": { "status": "good", "credits_used": 1 }
},
"webhook": { "name": "My Webhook" }
}
email_list.completed
{
"event_type": "email_list.completed",
"timestamp": "2026-01-01T12:05:00Z",
"data": {
"email_list": {
"id": "el_abc123",
"title": "January List",
"validation_type": "mx",
"total_emails": 500,
"good_emails": 480,
"bad_emails": 20,
"status": "completed",
"completed_at": "2026-01-01T12:05:00Z"
}
},
"webhook": { "name": "My Webhook" }
}
Verifying Signatures
Every delivery includes an X-Webhook-Signature header with an HMAC-SHA256 signature of the raw request body, prefixed with sha256=.
Always verify signatures before processing payloads.
Ruby
def valid_signature?(body, header, secret)
expected = "sha256=#{OpenSSL::HMAC.hexdigest("sha256", secret, body)}"
ActiveSupport::SecurityUtils.secure_compare(expected, header)
end
Node.js
const crypto = require("crypto");
function validSignature(body, header, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(body).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}
Python
import hmac, hashlib
def valid_signature(body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)
Failure Handling
MailCop retries failed deliveries up to 3 times with polynomial back-off. If all retries fail, the failure count increments by 1.
After 10 consecutive failures, the webhook is automatically disabled and you receive an email notification. To re-enable it, edit the webhook and toggle it back on. The failure counter resets to zero when you do.
A successful delivery resets the failure counter to zero.
Minimal Receiver Example
# Rails controller
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def mailcop
body = request.raw_post
header = request.headers["X-Webhook-Signature"]
secret = ENV["MAILCOP_WEBHOOK_SECRET"]
unless valid_signature?(body, header, secret)
head :unauthorized and return
end
payload = JSON.parse(body)
# process payload["event_type"] ...
head :ok
end
private
def valid_signature?(body, header, secret)
expected = "sha256=#{OpenSSL::HMAC.hexdigest("sha256", secret, body)}"
ActiveSupport::SecurityUtils.secure_compare(expected, header)
end
end