Webhooks
Receive real-time notifications when events happen in SuperSend
Webhooks allow your application to receive real-time HTTP notifications when events occur in SuperSend. Instead of polling our API, you subscribe to events and we send data to your endpoint as things happen.
V2 Webhook Management API
Quick Start
Via Dashboard
Via V2 API
curl -X POST 'https://api.supersend.io/v2/webhooks' \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"team_id": "your-team-id",
"url": "https://your-server.com/webhooks/supersend",
"name": "My Webhook",
"enabled": true,
"events": {
"open": true,
"click": true,
"reply": true,
"bounce": true,
"sent": false,
"unsubscribe": false
}
}'# Response (201 Created)
{
"success": true,
"data": {
"object": "webhook",
"id": "webhook-uuid",
"url": "https://your-server.com/webhooks/supersend",
"name": "My Webhook",
"enabled": true,
"events": {
"reply": true,
"open": true,
"click": true,
"sent": false,
"bounce": true,
"unsubscribe": false
},
"team_id": "team-uuid",
"created_at": "2025-01-19T10:00:00Z"
},
"request_id": "req_a1b2c3d4e5f6789012345678"
}
Webhook Management API (V2)
List Webhooks
curl -X GET 'https://api.supersend.io/v2/webhooks?team_id=xxx' \
-H "Authorization: Bearer YOUR_API_KEY"# Response (200 OK)
{
"success": true,
"data": [
{
"object": "webhook",
"id": "webhook-uuid",
"url": "https://your-server.com/webhooks",
"name": "My Webhook",
"enabled": true,
"events": [],
"secret": "*",
"headers": {},
"campaign_count": 3,
"config": {
"reply_event": true,
"open_event": true,
"click_event": true,
"sent_event": false,
"bounce_event": true,
"unsubscribe_event": false
},
"created_at": "2025-01-19T10:00:00Z",
"updated_at": "2025-01-19T10:00:00Z"
}
],
"pagination": {
"total": 1,
"limit": 50,
"offset": 0,
"has_more": false
},
"request_id": "req_..."
}
Get Webhook
curl -X GET 'https://api.supersend.io/v2/webhooks/webhook-uuid' \
-H "Authorization: Bearer YOUR_API_KEY"# Response (200 OK)
{
"success": true,
"data": {
"object": "webhook",
"id": "webhook-uuid",
"url": "https://your-server.com/webhooks",
"name": "My Webhook",
"enabled": true,
"events": [],
"secret": "*",
"headers": {},
"team_id": "team-uuid",
"campaign_id": null,
"config": {
"reply_event": true,
"open_event": true,
"click_event": true,
"sent_event": false,
"bounce_event": true,
"unsubscribe_event": false,
"webhook_enabled": true
},
"created_at": "2025-01-19T10:00:00Z",
"updated_at": "2025-01-19T10:00:00Z"
},
"request_id": "req_..."
}
Update Webhook
curl -X PATCH 'https://api.supersend.io/v2/webhooks/webhook-uuid' \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"events": {
"open": true,
"click": true,
"reply": true,
"bounce": true,
"unsubscribe": true,
"sent": false
}
}'Delete Webhook
curl -X DELETE 'https://api.supersend.io/v2/webhooks/webhook-uuid' \
-H "Authorization: Bearer YOUR_API_KEY"Test Webhook
Send a test event to verify your endpoint:
curl -X POST 'https://api.supersend.io/v2/webhooks/webhook-uuid/test' \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_type": "email_sent"
}'# Response (200 OK)
{
"success": true,
"data": {
"webhook_id": "webhook-uuid",
"url": "https://your-server.com/webhooks",
"event_type": "email_sent",
"test_result": {
"success": true,
"status_code": 200,
"status_text": "OK",
"response_time_ms": null
},
"payload_sent": {
"event": "email_sent",
"test": true,
"timestamp": "2025-01-19T14:32:15.123Z",
"data": {
"contact": {
"id": "00000000-0000-0000-0000-000000000000",
"email": "test@example.com",
"first_name": "Test",
"last_name": "Contact"
},
"campaign": {
"id": "00000000-0000-0000-0000-000000000000",
"name": "Test Campaign"
}
}
}
},
"request_id": "req_..."
}
Optional Request Body:
event_type (string, optional): Event type to test. Use the payload event type your handler will receive: email_sent, reply, open, click, bounce, unsubscribe, plus lifecycle/LinkedIn/Twitter types. Defaults to email_sent. Important: For "email successfully sent", the payload always uses type: "email_sent" (not sent)—handle email_sent in your code.Supported Event Types
Payload type field: what your endpoint receives
Every webhook POST body includes a type field. Use these values in your handler to branch on event type:
Payload type | When it fires |
|---|---|
open | Contact opened an email |
click | Contact clicked a link in an email |
reply | Contact replied to an email |
bounce | Email bounced |
email_sent | Email was successfully sent/delivered to the contact |
unsubscribe | Contact unsubscribed |
Important — email_sent not sent: For "email successfully sent", the payload always uses type: "email_sent". There is no sent event type in webhook payloads. If your code checks for sent, update it to handle email_sent so you receive these events.
V2 API configurable events
The V2 Webhook Management API lets you enable/disable the 6 email event types above. Set the corresponding event to true in the events object when creating or updating your webhook. The payload type you receive matches the names in the table (e.g. email_sent for "email sent").
Additional system events
These event types are also sent when enabled in your webhook configuration:
LinkedIn Events: linkedin_connection_sent, linkedin_connection_accepted, linkedin_message_sent, linkedin_reply, linkedin_profile_visited, linkedin_post_liked
Twitter Events: twitter_follow, twitter_unfollow, twitter_dm_sent, twitter_reply
Contact Lifecycle Events: contact_added, contact_paused, contact_resumed
Other Events: interest, appointment, finished, autoresponse
Webhook Payload
Every webhook POST request includes this payload structure:
{
"event_id": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
"timestamp": "2026-01-19T14:32:15.123Z",
"type": "open",
"CampaignId": "campaign-uuid",
"conversationId": "conversation-uuid",
"campaign": {
"id": "campaign-uuid",
"name": "Q4 Outreach",
"status": "active",
"tags": ["sales", "q4"]
},
"team": {
"id": "team-uuid",
"name": "Sales Team"
},
"contact": {
"id": "contact-uuid",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"company_name": "Acme Corp",
"title": "CEO",
"custom": {
"department": "Executive"
}
}
}Webhook Headers
Every webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-SuperSend-Event | The event type (e.g., open, click, reply) |
X-SuperSend-Timestamp | ISO 8601 timestamp of when the event occurred |
X-SuperSend-Signature | HMAC-SHA256 signature (if webhook secret is configured) |
Signature Format:
If you configured a webhook secret, the signature header will be:
X-SuperSend-Signature: sha256=<hex_signature>The signature is computed as: HMAC-SHA256(timestamp + "." + JSON.stringify(payload), secret)
Verifying Signatures:
const crypto = require('crypto');function verifyWebhookSignature(payload, signature, secret, timestamp) {
const signaturePayload = ${timestamp}.${JSON.stringify(payload)};
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signaturePayload)
.digest('hex');
const receivedSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
}
Event-Specific Data
Event-specific data is included in the payload when relevant. The full payload always includes event_id, timestamp, type, CampaignId, conversationId, campaign, team, and contact fields.
Click Events:
{
"event_id": "...",
"timestamp": "2026-01-19T14:32:15.123Z",
"type": "click",
"CampaignId": "...",
"click": {
"url": "https://calendly.com/yourcompany/30min",
"clicked_at": "2026-01-19T14:32:15.123Z"
},
"campaign": {...},
"team": {...},
"contact": {...}
}Reply Events:
{
"type": "reply",
"reply": {
"text": "Thanks for reaching out! I'd love to learn more...",
"received_at": "2026-01-19T14:32:15.123Z"
},
...
}Bounce Events:
Bounce payloads include bounce_category so you can distinguish true bounces from policy blocks:
invalid_recipient — Invalid or nonexistent address; counts toward bounce rate; contact is suppressed.policy_block — Blocked by recipient (e.g. SEG, Proofpoint); does not count toward bounce rate.soft — Temporary failure (e.g. mailbox full); retry later.{
"type": "bounce",
"bounce": {
"type": "hard",
"reason": "550 5.1.1 User unknown",
"bounced_at": "2026-01-19T14:32:15.123Z",
"bounce_category": "invalid_recipient"
},
...
}LinkedIn Connection Accepted:
{
"type": "linkedin_connection_accepted",
"linkedin": {
"profile_url": "https://linkedin.com/in/johnsmith",
"connection_sent_at": "2026-01-15T10:00:00.000Z",
"accepted_at": "2026-01-19T14:32:15.123Z"
},
...
}LinkedIn Message Sent:
{
"type": "linkedin_message_sent",
"linkedin": {
"profile_url": "https://linkedin.com/in/johnsmith",
"sent_at": "2026-01-19T14:32:15.123Z",
"message_preview": "Hi John, I wanted to reach out..."
},
...
}Twitter Follow:
{
"type": "twitter_follow",
"twitter": {
"profile_url": "@johndoe",
"followed_at": "2026-01-19T14:32:15.123Z",
"sequence_step": 1
},
...
}Contact Added:
{
"type": "contact_added",
"contact_added": {
"added_at": "2026-01-19T14:32:15.123Z",
"source": "api"
},
...
}Email Sent:
{
"type": "email_sent",
"email_sent": {
"sent_at": "2026-01-19T14:32:15.123Z",
"sequence_step": 1,
"subject": "Following up on our conversation",
"from_email": "sales@yourcompany.com"
},
...
}Handling Webhooks
Example Server (Node.js)
const express = require('express');
const app = express();
app.use(express.json());app.post('/webhooks/supersend', (req, res) => {
const event = req.body;
console.log(Received ${event.type} for contact ${event.contact.email});
switch (event.type) {
case 'open':
handleEmailOpened(event);
break;
case 'click':
handleEmailClicked(event);
break;
case 'reply':
handleContactReplied(event);
break;
case 'bounce':
handleEmailBounced(event);
break;
}
// Always respond with 200 to acknowledge receipt
res.status(200).send('OK');
});
app.listen(3000);
Example Server (Python)
from flask import Flask, requestapp = Flask(__name__)
@app.route('/webhooks/supersend', methods=['POST'])
def handle_webhook():
event = request.json
print(fclass="text-green-700 dark:text-green-400">"Received {event['type']} for {event['contact']['email']}")
if event['type'] == 'open':
handle_email_opened(event)
elif event['type'] == 'click':
handle_email_clicked(event)
elif event['type'] == 'reply':
handle_contact_replied(event)
elif event['type'] == 'bounce':
handle_email_bounced(event)
return 'OK', 200
Best Practices
1. Respond Quickly
Return a 200 response immediately, then process the event asynchronously:
app.post('/webhooks/supersend', (req, res) => {
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
processEventAsync(req.body);
});2. Handle Duplicates
Use event_id for idempotency:
const processedEvents = new Set();function handleEvent(event) {
if (processedEvents.has(event.event_id)) {
return; // Already processed
}
processedEvents.add(event.event_id);
// Process the event...
}
3. Implement Retry Logic
If your endpoint fails, SuperSend will retry. Make sure your processing is idempotent.
4. Monitor Webhook Health
Use the test endpoint to verify your webhook is working:
curl -X POST 'https://api.supersend.io/v2/webhooks/webhook-uuid/test' \
-H "Authorization: Bearer YOUR_API_KEY"Webhook Delivery Logs (V2 API)
SuperSend tracks all webhook delivery attempts, allowing you to monitor delivery status, retry failed webhooks, and debug issues.
List Webhook Deliveries
curl -X GET 'https://api.supersend.io/v2/webhooks/webhook-uuid/deliveries?team_id=xxx' \
-H "Authorization: Bearer YOUR_API_KEY"# Response (200 OK)
{
"success": true,
"data": [
{
"object": "webhook_delivery",
"id": "delivery-uuid",
"event_id": "event-uuid",
"event_type": "reply",
"status": "delivered",
"attempt_count": 1,
"max_attempts": 7,
"response_status": 200,
"response_time_ms": 150,
"error_message": null,
"created_at": "2026-01-19T10:00:00Z",
"last_attempt_at": "2026-01-19T10:00:00Z",
"delivered_at": "2026-01-19T10:00:00Z",
"next_retry_at": null,
"contact": {
"id": "contact-uuid",
"email": "john@example.com",
"name": "John Doe"
},
"campaign": {
"id": "campaign-uuid",
"name": "Q4 Outreach"
}
}
],
"pagination": {
"total": 100,
"limit": 50,
"offset": 0,
"has_more": true
},
"request_id": "req_..."
}
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
team_id | string (required) | Team ID |
status | string | Filter by status: pending, delivered, failed, retrying |
event_type | string | Filter by event type (e.g., reply, open, click) |
campaign_id | string | Filter by campaign ID |
limit | integer | Items per page (default: 50, max: 100) |
offset | integer | Pagination offset |
Get Webhook Delivery Details
curl -X GET 'https://api.supersend.io/v2/webhooks/webhook-uuid/deliveries/delivery-uuid?team_id=xxx' \
-H "Authorization: Bearer YOUR_API_KEY"# Response (200 OK)
{
"success": true,
"data": {
"object": "webhook_delivery",
"id": "delivery-uuid",
"event_id": "event-uuid",
"event_type": "reply",
"status": "delivered",
"attempt_count": 1,
"max_attempts": 7,
"payload": {
"event_id": "event-uuid",
"timestamp": "2026-01-19T10:00:00Z",
"type": "reply",
"CampaignId": "campaign-uuid",
"contact": {...},
"campaign": {...}
},
"response_status": 200,
"response_body": "OK",
"response_headers": {
"content-type": "text/plain"
},
"response_time_ms": 150,
"error_message": null,
"created_at": "2026-01-19T10:00:00Z",
"last_attempt_at": "2026-01-19T10:00:00Z",
"delivered_at": "2026-01-19T10:00:00Z"
},
"request_id": "req_..."
}
Retry Failed Delivery
Manually retry a failed webhook delivery:
curl -X POST 'https://api.supersend.io/v2/webhooks/webhook-uuid/deliveries/delivery-uuid/retry' \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"team_id": "your-team-id"
}'# Response (200 OK)
{
"success": true,
"data": {
"object": "webhook_delivery",
"id": "delivery-uuid",
"event_id": "event-uuid",
"event_type": "reply",
"status": "pending",
"attempt_count": 0
},
"message": "Retry queued successfully",
"request_id": "req_..."
}
Delivery Status Values
| Status | Description |
|---|---|
pending | Delivery is queued and waiting to be sent |
delivered | Webhook was successfully delivered (2xx response) |
failed | Delivery permanently failed after all retry attempts |
retrying | Delivery failed but will be retried automatically |
Automatic Retry Schedule
Failed deliveries are automatically retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 15 minutes |
| 5 | 1 hour |
| 6 | 4 hours |
| 7 | 24 hours (final) |
After 7 failed attempts, the delivery is marked as permanently failed.
Dashboard Access
Troubleshooting
Webhook not receiving events?
Missing events?
Failed deliveries?