company logo

Help center

Go to skysenders.ai
Β©2025 skysenders pvt. limited. All rights reserved. Made in India. Made for Deliverability.
All collectionsWorkspaceDeveloper OptionsWebhooksπŸš€ Getting Started with Webhooks

πŸš€ Getting Started with Webhooks

Complete guide to receiving real-time event notifications, verifying HMAC-SHA256 signatures, request headers, payload formats, and testing.

Webhooks allow your application to receive real-time HTTP notifications whenever events happen in your accountβ€”such as when a lead replies to an email, a link is clicked, an in-thread reply is dispatched, or an account status changes.

Instead of continuously polling our REST APIs, webhooks push events directly to your server as soon as they occur.

πŸ“˜ API Reference: View complete endpoints and schemas in the Webhook API Documentation.


Supported Destinations

SkySenders supports two types of webhook destinations:

  • HTTP/HTTPS Webhooks (Http): Sends raw JSON payloads to your custom server endpoint with optional HMAC-SHA256 signature verification.

  • Slack Incoming Webhooks (Slack): Automatically formats event data using Slack Block Kit cards, including lead context, campaign information, and status badges.

Supported Events

You can subscribe a webhook to any combination of the following events:

Email Events

  • email.sent: Triggered when an email is sent to a lead.

  • email.opened: Triggered the first time a lead opens an email.

  • email.link_clicked: Triggered the first time a lead clicks any tracked link.

  • email.bounced: Triggered when an email hard bounces or fails delivery.

  • email.reply: Triggered when a lead sends a reply (includes AI sentiment & category).

  • email.reply_sent: Triggered when a reply is sent back to the lead in an email thread.

  • email.unsubscribed: Triggered when a lead unsubscribes.

  • email.disconnected: Triggered if a mailbox disconnects (e.g., revoked OAuth or expired token).

Campaign Events

  • campaign.status_changed: Triggered when a campaign is paused, resumed, completed, or errors.

LinkedIn Events

  • linkedin.connection_sent: Triggered when a connection invitation is sent.

  • linkedin.connection_accepted: Triggered when a lead accepts your LinkedIn connection request.

  • linkedin.message_sent: Triggered when a direct message is sent on LinkedIn.

  • linkedin.inmail_sent: Triggered when an InMail message is sent.

  • linkedin.reply: Triggered when a lead replies to a LinkedIn message.

  • linkedin.disconnected: Triggered if a LinkedIn account disconnects or requires checkpoint verification.


Standard Payload Format

All HTTP webhook notifications follow a unified JSON envelope:

{
  "event": "email.reply",
  "timestamp": "2026-10-10T10:00:00.000Z",
  "workspace_id": 14,
  "data": {
    "lead": {
      "contact_id": 1042,
      "email": "[email protected]"
    },
    "sequence": {
      "id": 85
    },
    "mailbox_email": "[email protected]",
    "message": {
      "id": 50123,
      "from": "[email protected]",
      "to": "[email protected]",
      "subject": "Re: Quick question regarding your stack",
      "preview": "Thanks for reaching out! Let's schedule a demo next Tuesday.",
      "reply_category_id": 1,
      "is_ai_categorised": true,
      "received_at": "2026-10-10T10:00:00.000Z"
    }
  }
}

Request Headers

Every webhook POST request delivered to your endpoint includes the following HTTP headers:

  • Content-Type: Always application/json

  • User-Agent: Outreach-Webhook/1.0

  • X-Webhook-Id: The ID of the configured webhook in your workspace.

  • X-Webhook-Event: The event type name (e.g. email.reply, campaign.status_changed).

  • X-Webhook-Signature: HMAC-SHA256 signature header (included when a secret is configured).

  • X-Webhook-Test: Set to true if this is a manual test dispatch from the dashboard.


Securing Your Webhooks (HMAC-SHA256 Verification)

To ensure incoming webhook requests genuinely originate from us and have not been altered in transit, we recommend setting a Secret when creating your webhook.

When a secret is configured (format: ws_...), SkySenders calculates an HMAC-SHA256 signature and attaches it in the X-Webhook-Signature header:

Header Format: X-Webhook-Signature: t=1725010130,v1=5d41402abc4b2a76b9719d911017c5926ebd896b02a6cf4971c6dc0d6df6cf25

  • t: Unix timestamp (in seconds) of when the request was dispatched.

  • v1: The HMAC-SHA256 hexadecimal hash computed over "${t}.${rawRequestBody}".

Step-by-Step Verification

  • 1. Extract t (timestamp) and v1 (signature) from the X-Webhook-Signature header.

  • 2. Verify that the timestamp is within your tolerance threshold (we recommend 300 seconds / 5 minutes) to protect against replay attacks.

  • 3. Compute the HMAC-SHA256 hash using your webhook secret over the string ${t}.${raw_request_body}.

  • 4. Compare your calculated hash with v1 using a timing-safe equality check.

Implementation Examples

Node.js / Express

const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  if (!signatureHeader || !secret) return false;

  const parts = Object.fromEntries(
    signatureHeader.split(',').map(param => param.split('='))
  );

  const timestamp = parseInt(parts.t, 10);
  const receivedSignature = parts.v1;
  if (!timestamp || !receivedSignature) return false;

  // Protect against replay attacks (5-minute tolerance)
  const currentTimestamp = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTimestamp - timestamp) > toleranceSeconds) {
    return false;
  }

  // Compute expected HMAC-SHA256
  const payloadToSign = `${timestamp}.${typeof rawBody === 'string' ? rawBody : JSON.stringify(rawBody)}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payloadToSign, 'utf8')
    .digest('hex');

  // Constant-time comparison
  const expectedBuffer = Buffer.from(expectedSignature, 'utf8');
  const receivedBuffer = Buffer.from(receivedSignature, 'utf8');
  if (expectedBuffer.length !== receivedBuffer.length) return false;

  return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}

Python / Flask / FastAPI

import hmac
import hashlib
import time

def verify_webhook(raw_body: str, signature_header: str, secret: str, tolerance_seconds: int = 300) -> bool:
    if not signature_header or not secret:
        return False

    parts = dict(item.split("=") for item in signature_header.split(","))
    timestamp = int(parts.get("t", 0))
    received_signature = parts.get("v1", "")

    # Replay protection (5-minute tolerance)
    if abs(int(time.time()) - timestamp) > tolerance_seconds:
        return False

    payload_to_sign = f"{timestamp}.{raw_body}".encode("utf-8")
    expected_signature = hmac.new(
        secret.encode("utf-8"),
        payload_to_sign,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected_signature, received_signature)

Testing Webhooks

You can test webhooks at any time without having to wait for live lead interactions:

  • Test Pre-Save URL: Send simulated events to test an arbitrary URL directly from the creation modal before saving it.

  • Test Existing Webhook: In the dashboard, click "Send Test Event" on any existing webhook and pick the event type you want to simulate.

  • Test Deliveries Include: "is_test": true in the JSON payload, X-Webhook-Test: true in the HTTP header, and a [TEST WEBHOOK] banner block if delivered to a Slack channel.

Delivery, Retries & What's Coming Soon

Delivery Expectations

Your webhook listener should respond with an HTTP 2xx status code (200 OK, 202 Accepted, or 204 No Content) as quickly as possible (recommended under 3 seconds). Heavy background processing (e.g., CRM syncing or database updates) should be offloaded to an asynchronous background worker or queue on your end.

Upcoming: Automated Retry Mechanism & Control Settings

We are actively rolling out an enhanced Retry & Delivery Engine with dedicated controls:

  • Automatic Retries with Exponential Backoff: If your server returns a 5xx error, drops connection, or times out, we will automatically retry the delivery multiple times across an exponential backoff schedule so you never miss an event.

  • Configurable Retry Settings: Choose retry counts and thresholds that match your infrastructure needs.

  • Detailed Execution & Failure Logs: A dedicated log viewer inside your dashboard showing exact request payloads, response codes, latency in milliseconds, and error details for failed deliveries.


Best Practices Checklist

  • Verify Signatures: Always validate the X-Webhook-Signature header to ensure requests are authentic.

  • Acknowledge Quickly: Return HTTP 200 immediately and process work in a background worker or queue.

  • Handle Duplicates (Idempotency): Use the combination of event timestamp and identifiers (like message.id or lead.contact_id) to ensure your system handles potential duplicate requests gracefully.

  • Use HTTPS: Ensure your webhook endpoints use SSL/TLS (https://) in production environments.

Did this answer your question?
😞
😐
😁