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

V2 provides full CRUD operations for managing webhooks programmatically.


Quick Start

Via Dashboard

  • Go to your campaign's Integrations settings

  • Add a Webhook integration

  • Enter your webhook URL

  • Select which events to receive

  • Save and test your webhook
  • Via V2 API

    bash
    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

    bash
    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

    bash
    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

    bash
    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

    bash
    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:

    bash
    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:

    TypeDescription

    openContact opened an email
    clickContact clicked a link in an email
    replyContact replied to an email
    bounceEmail bounced
    sentEmail was successfully sent
    unsubscribeContact unsubscribed

    Note: 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 sent

  • linkedin_connection_accepted - Connection request accepted

  • linkedin_message_sent - LinkedIn message or InMail sent

  • linkedin_reply - Contact replied on LinkedIn

  • linkedin_profile_visited - Profile visit completed

  • linkedin_post_liked - Post like completed
  • Twitter Events:

  • twitter_follow - Followed contact on Twitter

  • twitter_unfollow - Unfollowed contact

  • twitter_dm_sent - Direct message sent

  • twitter_reply - Contact replied via DM
  • Contact Lifecycle Events:

  • contact_added - Contact added to campaign

  • contact_paused - Contact paused in campaign

  • contact_resumed - Contact resumed in campaign

  • email_sent - Email successfully delivered (separate from sent event)
  • Other Events:

  • interest - Interest level changed

  • appointment - Appointment booked

  • finished - Contact completed the sequence

  • autoresponse - Out-of-office detected
  • These additional events are automatically sent when enabled in your webhook configuration (enabled by default).


    Webhook Payload

    Every webhook POST request includes this payload structure:

    json
    {
    "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:

    HeaderDescription

    Content-Typeapplication/json
    X-SuperSend-EventThe event type (e.g., open, click, reply)
    X-SuperSend-TimestampISO 8601 timestamp of when the event occurred
    X-SuperSend-SignatureHMAC-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:

    javascript
    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:

    json
    {
    "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:

    json
    {
    "type": "reply",
    "reply": {
    "text": "Thanks for reaching out! I'd love to learn more...",
    "received_at": "2026-01-19T14:32:15.123Z"
    },
    ...
    }

    Bounce Events:

    json
    {
    "type": "bounce",
    "bounce": {
    "type": "hard",
    "reason": "550 5.1.1 User unknown",
    "bounced_at": "2026-01-19T14:32:15.123Z"
    },
    ...
    }

    LinkedIn Connection Accepted:

    json
    {
    "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:

    json
    {
    "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:

    json
    {
    "type": "twitter_follow",
    "twitter": {
    "profile_url": "@johndoe",
    "followed_at": "2026-01-19T14:32:15.123Z",
    "sequence_step": 1
    },
    ...
    }

    Contact Added:

    json
    {
    "type": "contact_added",
    "contact_added": {
    "added_at": "2026-01-19T14:32:15.123Z",
    "source": "api"
    },
    ...
    }

    Email Sent:

    json
    {
    "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)

    javascript
    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)

    python
    from flask import Flask, request

    app = 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:

    javascript
    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:

    javascript
    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:

    bash
    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

    bash
    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:

    ParameterTypeDescription

    team_idstring (required)Team ID
    statusstringFilter by status: pending, delivered, failed, retrying
    event_typestringFilter by event type (e.g., reply, open, click)
    campaign_idstringFilter by campaign ID
    limitintegerItems per page (default: 50, max: 100)
    offsetintegerPagination offset

    Get Webhook Delivery Details

    bash
    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:

    bash
    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

    StatusDescription

    pendingDelivery is queued and waiting to be sent
    deliveredWebhook was successfully delivered (2xx response)
    failedDelivery permanently failed after all retry attempts
    retryingDelivery failed but will be retried automatically

    Automatic Retry Schedule

    Failed deliveries are automatically retried with exponential backoff:

    AttemptDelay

    1Immediate
    230 seconds
    32 minutes
    415 minutes
    51 hour
    64 hours
    724 hours (final)

    After 7 failed attempts, the delivery is marked as permanently failed.

    ℹ️

    Dashboard Access

    You can also view delivery logs and retry failed webhooks directly in the SuperSend dashboard:
  • Go to Admin > Integrations

  • Click on your webhook integration

  • Select the Delivery Logs tab

  • Troubleshooting

    Webhook not receiving events?

  • Verify your endpoint is publicly accessible

  • Check that HTTPS is properly configured

  • Ensure you're returning a 200 status code

  • Verify the webhook is active in your settings

  • Check the Delivery Logs tab in the dashboard for errors
  • Missing events?

  • Check if specific event types are enabled

  • Verify team_id is correct

  • Check your server logs for errors
  • Failed deliveries?

  • Review the error message in the delivery logs

  • Verify your endpoint returns a 2xx status code

  • Check for timeouts (webhook must respond within 30 seconds)

  • Use the retry button or API to manually retry failed deliveries