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": "sent"
}'# Response (200 OK)
{
"success": true,
"data": {
"webhook_id": "webhook-uuid",
"url": "https://your-server.com/webhooks",
"event_type": "sent",
"test_result": {
"success": true,
"status_code": 200,
"status_text": "OK",
"response_time_ms": null
},
"payload_sent": {
"event": "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 (reply, open, click, sent, bounce, unsubscribe). Defaults to sent.Supported Event Types
V2 API Configurable Events
The V2 Webhook Management API allows you to configure these 6 email event types:
openclickreplybouncesentunsubscribeNote: To receive events, set the corresponding event type to true in the events object when creating or updating your webhook.
Additional System Events
The webhook system also supports additional event types that are enabled by default (not configurable via V2 API):
LinkedIn Events:
linkedin_connection_sent - Connection request sentlinkedin_connection_accepted - Connection request acceptedlinkedin_message_sent - LinkedIn message or InMail sentlinkedin_reply - Contact replied on LinkedInlinkedin_profile_visited - Profile visit completedlinkedin_post_liked - Post like completedTwitter Events:
twitter_follow - Followed contact on Twittertwitter_unfollow - Unfollowed contacttwitter_dm_sent - Direct message senttwitter_reply - Contact replied via DMContact Lifecycle Events:
contact_added - Contact added to campaigncontact_paused - Contact paused in campaigncontact_resumed - Contact resumed in campaignemail_sent - Email successfully delivered (separate from sent event)Other Events:
interest - Interest level changedappointment - Appointment bookedfinished - Contact completed the sequenceautoresponse - Out-of-office detectedThese additional events are automatically sent when enabled in your webhook configuration (enabled by default).
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:
Content-Typeapplication/jsonX-SuperSend-Eventopen, click, reply)X-SuperSend-TimestampX-SuperSend-SignatureSignature 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:
{
"type": "bounce",
"bounce": {
"type": "hard",
"reason": "550 5.1.1 User unknown",
"bounced_at": "2026-01-19T14:32:15.123Z"
},
...
}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:
team_idstatuspending, delivered, failed, retryingevent_typereply, open, click)campaign_idlimitoffsetGet 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
pendingdeliveredfailedretryingAutomatic Retry Schedule
Failed deliveries are automatically retried with exponential backoff:
After 7 failed attempts, the delivery is marked as permanently failed.
Dashboard Access
Troubleshooting
Webhook not receiving events?
Missing events?
Failed deliveries?