Webhooks
Send SendCoop events such as new subscribers, clicks and finished campaigns to any URL, and verify each delivery with the HMAC-SHA256 signature.
A webhook sends a message to another app the moment something happens in SendCoop, such as a new subscriber or a link click. Each message is a JSON payload, signed so you can check it really came from SendCoop.
Add a webhook
- In the left sidebar, click Integrations, then the Automation apps tab.
- Under Add a webhook, choose When this happens (the event).
- Optional: choose a list under For list. Leave it as All lists to get events from every list.
- Enter the address under Send to this URL. It must be a public
http://orhttps://address. - Click Add webhook.
- Click Send test so your app receives a sample payload.
Each webhook row has Send test, Pause or Resume, Delete, and Copy secret for its Signing secret. You can have up to 100 webhooks.
Events
| Event | Shown in SendCoop as |
|---|---|
subscriber.subscribed |
Contact subscribed |
subscriber.unsubscribed |
Contact unsubscribed |
subscriber.tagged |
Contact tags changed |
subscriber.updated |
Contact details updated |
email.opened |
Campaign email opened |
email.clicked |
Link clicked in a campaign |
campaign.sent |
Campaign finished sending |
All events except campaign.sent can be limited to one list.
What SendCoop sends
Each delivery is an HTTP POST with a JSON body:
{
"id": "5f0c1a7e-2b7d-4c55-9a51-0d8f3f6b2c11",
"event": "subscriber.subscribed",
"occurred_at": "2026-10-10T09:30:00+00:00",
"data": {
"subscriber": {
"id": 123,
"uid": "abc123",
"email": "jane@example.com",
"status": "subscribed",
"source": "form",
"tags": ["customer"],
"fields": { "FIRST_NAME": "Jane", "LAST_NAME": "Doe" },
"first_name": "Jane",
"last_name": "Doe",
"created_at": "2026-10-10T09:30:00+00:00"
},
"list": { "uid": "def456", "name": "Newsletter" }
}
}
email.openedandemail.clickedalso include acampaignobject (uid,name,subject,status,sent_at).email.clickedadds the clickedurl.subscriber.updatedaddschanged, a list of the fields that changed.campaign.sentcontains only thecampaignobject.
These headers come with every request:
X-SendCoop-Event: the event name.X-SendCoop-Delivery: a unique ID for this delivery (the same asidin the body).X-SendCoop-Signature:sha256=followed by the signature.Content-Type: application/json
Verify the signature
The signature is an HMAC-SHA256 of the raw request body, using the webhook’s Signing secret as the key, written in hex. Compute the same value on your side and compare. Always use the raw body exactly as received, before any JSON parsing.
Node.js (Express)
const crypto = require('crypto');
const express = require('express');
const app = express();
const SECRET = process.env.SENDCOOP_WEBHOOK_SECRET;
app.post('/sendcoop', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(req.body) // raw Buffer
.digest('hex');
const received = req.get('X-SendCoop-Signature') || '';
const valid = received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body.toString('utf8'));
console.log(payload.event, payload.data);
res.sendStatus(200);
});
app.listen(3000);
PHP
<?php
$secret = getenv('SENDCOOP_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
$received = $_SERVER['HTTP_X_SENDCOOP_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($body, true);
// $payload['event'], $payload['data']
http_response_code(200);
Retries and failures
- Reply with any
2xxstatus to confirm you received the event. SendCoop waits up to 15 seconds and does not follow redirects. - If your server times out or replies with a
5xxerror, SendCoop tries again twice: after about 1 minute, then after about 5 minutes. - A
4xxreply is not retried. - A
410 Gonereply deletes the webhook. Use it to tell SendCoop to stop sending. - After 50 failed deliveries in a row, the webhook is switched off. Fix the address and click Resume.
The Last delivery column shows the result of the most recent attempt.
Tips
- Use the
X-SendCoop-DeliveryID to ignore a delivery you have already processed. - Apps that connect with your API key, such as the Zapier app, add their own webhooks to this page automatically.