NEWAutomation apps: connect SendCoop to Zapier, Pabbly, Make and n8n.New: Zapier, Pabbly, Make, n8nSee how →

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

  1. In the left sidebar, click Integrations, then the Automation apps tab.
  2. Under Add a webhook, choose When this happens (the event).
  3. Optional: choose a list under For list. Leave it as All lists to get events from every list.
  4. Enter the address under Send to this URL. It must be a public http:// or https:// address.
  5. Click Add webhook.
  6. 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.opened and email.clicked also include a campaign object (uid, name, subject, status, sent_at). email.clicked adds the clicked url.
  • subscriber.updated adds changed, a list of the fields that changed.
  • campaign.sent contains only the campaign object.

These headers come with every request:

  • X-SendCoop-Event: the event name.
  • X-SendCoop-Delivery: a unique ID for this delivery (the same as id in 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 2xx status 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 5xx error, SendCoop tries again twice: after about 1 minute, then after about 5 minutes.
  • A 4xx reply is not retried.
  • A 410 Gone reply 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-Delivery ID 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.
Still need help? Email hello@sendcoop.com and we usually reply within one business day.