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": "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 typeWhen it fires
    openContact opened an email
    clickContact clicked a link in an email
    replyContact replied to an email
    bounceEmail bounced
    email_sentEmail was successfully sent/delivered to the contact
    unsubscribeContact 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:

    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:

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

    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