> ## Documentation Index
> Fetch the complete documentation index at: https://docs.autosend.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Use webhooks to notify your application about email and contact events in real-time.

export const LOCALTUNNEL_LINK = "https://localtunnel.github.io";

export const NGROK_LINK = "https://ngrok.com";

export const APP_PATHS = {
  home: '/',
  quickstart: '/quickstart',
  domainConfiguration: '/domain',
  apiReference: '/api-reference',
  sendEmail: '/api-reference/mails/send',
  bulkSendEmail: '/api-reference/mails/bulk',
  upsertContactApiRef: '/api-reference/contacts/upsert-contact',
  transactional: '/transactional-emails',
  emailActivity: '/transactional-emails/email-activity',
  emailTemplates: '/transactional-emails/email-templates',
  sendingEmail: '/quickstart/email-using-api',
  transactionalTroubleshooting: '/transactional-emails/troubleshooting',
  marketing: '/marketing-emails',
  campaigns: '/marketing-emails/campaigns',
  contacts: '/marketing-emails/contacts',
  contactsIntroduction: '/marketing-emails/contacts/introduction',
  contactsImportCsv: '/marketing-emails/contacts/import-csv',
  contactsLists: '/marketing-emails/contacts/lists',
  contactsSegments: '/marketing-emails/contacts/segments',
  contactsCustomFields: '/marketing-emails/contacts/custom-fields',
  sender: '/marketing-emails/sender',
  unsubscribeGroups: '/others/unsubscribe-groups',
  webhookIntroduction: '/others/webhooks/introduction',
  webhookEventType: '/others/webhooks/event-type',
  webhookRetries: '/others/webhooks/retries',
  webhookVerifyRequests: '/others/webhooks/verify-requests',
  dynamicTemplates: '/dynamic-templates',
  guides: '/guides',
  sitemap: '/sitemap.xml',
  team: '/others/team',
  automations: '/automations',
  smtpIntroduction: '/quickstart/smtp',
  betterAuth: '/guides/better-auth',
  templateVariables: '/transactional-emails/variables',
  suppressions: '/others/suppressions',
  rateLimit: '/api-reference/rate-limit',
  nodejsSdk: '/sdk/nodejs',
  smtpIntegrationGuides: '/guides/smtp',
  apiKeys: '/api-keys',
  apiReferenceIntroduction: '/api-reference/introduction',
  lovableGuide: '/ai/integrations/lovable',
  aiIntroduction: '/ai/introduction',
  aiSkills: '/ai/skills',
  aiMcpServer: '/ai/mcp-server',
  aiLovable: '/ai/integrations/lovable',
  aiBolt: '/ai/integrations/bolt',
  aiV0: '/ai/integrations/v0',
  aiReplit: '/ai/integrations/replit',
  mcpClaude: '/ai/mcp-clients/claude',
  mcpCursor: '/ai/mcp-clients/cursor',
  mcpCopilot: '/ai/mcp-clients/copilot',
  mcpWindsurf: '/ai/mcp-clients/windsurf',
  mcpCodex: '/ai/mcp-clients/codex',
  mcpAntigravity: '/ai/mcp-clients/antigravity',
  mcpChatgpt: '/ai/mcp-clients/chatgpt',
  mcpRaycast: '/ai/mcp-clients/raycast',
  domainWarmup: '/marketing-emails/domain-warmup',
  projects: '/projects',
  createAutomationApi: '/api-reference/automations/create-automation'
};

export const AUTOSEND_PATHS = {
  dashboard: 'https://autosend.com/dashboard',
  apiKey: 'https://autosend.com/account/api-key',
  faqs: 'https://autosend.com/faq',
  marketingEmails: 'https://autosend.com/marketing-emails',
  webhooks: 'https://autosend.com/webhooks',
  composeByAutoSend: 'https://autosend.com/compose',
  emailActivity: 'https://autosend.com/email-activities',
  team: 'https://autosend.com/settings/team',
  pricing: 'https://autosend.com/pricing',
  verifyEmail: 'https://autosend.com/compose/email-builder?template=verify-email',
  welcomeEmail: 'https://autosend.com/compose/email-builder?template=welcome-email',
  productUpdate: 'https://autosend.com/compose/email-builder?template=product-update',
  newsletter: 'https://autosend.com/compose/email-builder?template=newsletter',
  automations: 'https://autosend.com/automations',
  globalSuppressions: 'https://autosend.com/suppressions/global',
  signup: 'https://autosend.com/signup',
  domains: 'https://autosend.com/settings/domains',
  logoKit: 'https://asend.email/logo',
  contactsPage: 'https://autosend.com/contacts/list-and-segments'
};

## What is a webhook?

Webhooks are HTTP callbacks that send real-time notifications to your application when specific events occur. Instead of continuously polling the AutoSend API to check for updates, webhooks push data to your application the moment events happen.

All AutoSend webhooks use HTTPS and deliver a JSON payload that your application can process immediately.

### Why use webhooks? Common use cases:

* **Automatically remove bounced email addresses** from your mailing lists
* **Track email engagement** in real-time (opens, clicks, unsubscribes)
* **Sync contact changes** across multiple systems
* **Create alerts** in your messaging or incident tools based on event types
* **Store all events** in your own database for custom reporting and retention
* **Trigger workflows** when specific events occur (e.g., send a Slack notification when an email bounces)
* **Maintain compliance logs** for audit purposes

***

## How to set up webhooks

<Steps>
  <Step titleSize="h3" title="Create a local endpoint to receive requests">
    Create a new route in your application that accepts POST requests.

    <CodeGroup>
      ```javascript NodeJs expandable theme={null}
      const express = require('express');
      const crypto = require('crypto');
      const app = express();

      app.use(express.json());

      app.post('/webhooks/autosend', (req, res) => {
        // Verify signature (see Verify Webhook Requests section)
        const signature = req.headers['x-webhook-signature'];
        const isValid = verifySignature(
          req.body,
          signature,
          process.env.WEBHOOK_SECRET
        );

        if (!isValid) {
          return res.status(401).json({ error: 'Invalid signature' });
        }

        // Process the webhook
        const { event, data } = req.body;
        console.log(`Received ${event} event:`, data);

        // Process based on event type
        switch (event) {
          case 'email.opened':
            // Handle email opened
            break;
          case 'email.clicked':
            // Handle email clicked
            break;
          case 'contact.created':
            // Handle contact created
            break;
          // ... handle other events
        }

        // Always respond quickly with 2xx status
        res.status(200).json({ received: true });
      });

      function verifySignature(body, signature, secret) {
        const expectedSignature = crypto
          .createHmac('sha256', secret)
          .update(JSON.stringify(body))
          .digest('hex');
        return signature === expectedSignature;
      }

      app.listen(3000, () => {
        console.log('Webhook endpoint listening on port 3000');
      });
      ```

      ```javascript NextJs expandable theme={null}
      // pages/api/webhooks.js
      import crypto from 'crypto';

      export default async function handler(req, res) {
        if (req.method !== 'POST') {
          return res.status(405).json({ error: 'Method not allowed' });
        }

        // Verify signature
        const signature = req.headers['x-webhook-signature'];
        const webhookSecret = process.env.WEBHOOK_SECRET;

        const expectedSignature = crypto
          .createHmac('sha256', webhookSecret)
          .update(JSON.stringify(req.body))
          .digest('hex');

        if (signature !== expectedSignature) {
          return res.status(401).json({ error: 'Invalid signature' });
        }

        // Process webhook
        const { event, data } = req.body;
        console.log(`Received ${event} event:`, data);

        // Respond immediately
        res.status(200).json({ received: true });
      }
      ```

      ```python Python expandable theme={null}
      from flask import Flask, request, jsonify
            import hmac
            import hashlib
            import os

            app = Flask(__name__)
            WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')

            def verify_signature(payload, signature):
                expected = hmac.new(
                    WEBHOOK_SECRET.encode('utf-8'),
                    payload.encode('utf-8'),
                    hashlib.sha256
                ).hexdigest()
                return hmac.compare_digest(expected, signature)

            @app.route('/webhooks/autosend', methods=['POST'])
            def webhook():
                signature = request.headers.get('X-Webhook-Signature')
                payload = request.get_data(as_text=True)

                if not signature or not verify_signature(payload, signature):
                    return jsonify({'error': 'Invalid signature'}), 401

                data = request.json
                event = data.get('event')

                print(f'Received {event} event')

                return jsonify({'received': True}), 200
      ```
    </CodeGroup>

    <Info>
      When you receive an event, respond with an `HTTP 200 OK` status within 10
      seconds to confirm successful delivery.
    </Info>
  </Step>

  <Step titleSize="h3" title="Register your webhook endpoint">
    1. Go to <a href={AUTOSEND_PATHS.webhooks} title="Webhook AutoSend" target="_blank" rel="noopener noreferrer">Webhooks</a> in the AutoSend sidebar

    <Frame>
      <img src="https://mintcdn.com/autosend-13920f5c/YIEU6RAQ5pKJQ7s4/images/webhook-home.png?fit=max&auto=format&n=YIEU6RAQ5pKJQ7s4&q=85&s=a5129ebcd04d056fa785bf141e39e3aa" alt="" width="2000" height="1260" data-path="images/webhook-home.png" />
    </Frame>

    2. Click **"New Webhook"**
    3. Fill in the webhook details:
       * **Name**: A friendly name for identification (e.g., "Production Email Tracker")
       * **Endpoint URL**: Your publicly accessible HTTPS URL. For local development, use a tunneling service like <a href={NGROK_LINK} target="_blank" title="ngrok">ngrok</a> or <a href={LOCALTUNNEL_LINK} target="_blank" title="localtunnel">localtunnel</a>.
       * **Events**: Select the events you want to receive notifications for
    4. Click **"Create Webhook"**

    <Frame>
      <img src="https://mintcdn.com/autosend-13920f5c/YIEU6RAQ5pKJQ7s4/images/create-new-webhook.png?fit=max&auto=format&n=YIEU6RAQ5pKJQ7s4&q=85&s=94c17c6d8722f61c3ec75df01f8127de" alt="" width="2000" height="1260" data-path="images/create-new-webhook.png" />
    </Frame>

    5. Copy the secret token and store it securely. You'll need it to verify webhook requests.

    <Note>
      Store your webhook secret as an environment variable
      (`WEBHOOK_SECRET=your_secret_here`). Never commit it to version control. In
      production, use a secret management service like AWS Secrets Manager.
    </Note>
  </Step>

  <Step titleSize="h3" title="Test your webhook">
    Add logging to your endpoint to verify incoming requests:

    ```javascript theme={null}
    app.post('/webhooks/autosend', (req, res) => {
      console.log('Webhook received!');
      console.log('Event:', req.body.event);
      console.log('Data:', JSON.stringify(req.body.data, null, 2));
      res.status(200).json({ received: true });
    });
    ```

    Trigger test events by performing actions in AutoSend:

    * **For email events**: Send a test campaign or transactional email
    * **For contact events**: Create, update, or delete a test contact

    Watch your logs to confirm your endpoint receives the webhook requests.
  </Step>

  <Step titleSize="h3" title="Deploy to production">
    After testing locally, deploy your webhook endpoint to your production environment (Vercel, Netlify, AWS Lambda, Railway, etc.).

    **Your production endpoint must:**

    * Use HTTPS (required)
    * Respond within 10 seconds
    * Implement proper error handling
    * Verify webhook signatures

    Once deployed, create a new webhook in AutoSend with your production URL. If you used a tunneling URL for development, you'll need to register a separate webhook for production.
  </Step>
</Steps>

***

## Managing webhooks

From the <a href={AUTOSEND_PATHS.webhooks} title="Webhook AutoSend" target="_blank" rel="noopener noreferrer">Webhooks</a> page, you can:

* **View all webhooks** for your project, displayed as cards with webhook details
* **Edit webhooks**: Click the menu (⋮) on a webhook card and select "Edit"
* **Delete webhooks**: Click the menu (⋮) and select "Delete"
* **Check webhook status**: Each card shows whether the webhook is "Enabled" or "Disabled"
* **Copy webhook details**: The webhook ID and URL are copyable from each card

<Note>
  If you lose your webhook secret, you can reveal it by editing the webhook and
  clicking "Reveal Secret".
</Note>

***

## Best practices

<AccordionGroup>
  <Accordion title="Respond quickly">
    Process webhooks asynchronously so you can respond within 10 seconds:

    ```javascript theme={null}
    // ❌ Bad: Synchronous processing
    app.post("/webhooks/autosend", async (req, res) => {
      await updateDatabase(data);
      await sendToAnalytics(data);
      res.status(200).json({ received: true });
    });

    // ✅ Good: Asynchronous processing
    app.post("/webhooks/autosend", async (req, res) => {
      // Queue for background processing
      await queue.add("process-webhook", { event, data });

      // Respond immediately
      res.status(200).json({ received: true });
    });
    ```
  </Accordion>

  <Accordion title="Always verify signatures">
    Never trust incoming webhook requests without verification. See the <a href={APP_PATHS.webhookVerifyRequests} title="Verify Webhook Requests">Verify Webhook Requests</a> guide for implementation details.
  </Accordion>

  <Accordion title="Log everything">
    Maintain detailed logs for debugging:

    ```javascript theme={null}
    app.post("/webhooks/autosend", (req, res) => {
      logger.info(
        {
          deliveryId: req.headers["x-webhook-delivery-id"],
          event: req.body.event,
          timestamp: new Date().toISOString(),
        },
        "Webhook received"
      );

      // Process webhook
    });
    ```
  </Accordion>

  <Accordion title="Monitor webhook health">
    Track webhook delivery success and failure rates. AutoSend automatically tracks `failureCount`, `lastSuccessAt`, and `lastFailedAt` for each webhook. See <a href={APP_PATHS.webhookRetries} title="Retries and Replays">Retries and Replays</a> for monitoring examples.
  </Accordion>
</AccordionGroup>

***

## FAQ

<AccordionGroup>
  <Accordion title="What events can I subscribe to?">
    AutoSend supports email engagement events (opened, clicked, bounced, delivered, etc.) and contact events (created, updated, deleted). See the <a href={APP_PATHS.webhookEventType} title="Event Types">Event Types</a> guide for the complete list.
  </Accordion>

  <Accordion title="What is the retry schedule?">
    If AutoSend doesn't receive a 200 response from your webhook endpoint, it automatically retries delivery up to 3 times with exponential backoff (1 min, 5 min, 15 min). See <a href={APP_PATHS.webhookRetries} title="Retries and Replays">Retries and Replays</a> for details.
  </Accordion>

  <Accordion title="Can I have multiple webhooks?">
    Yes, you can create up to 100 webhooks per project. Each webhook can subscribe to different events and point to different endpoints.
  </Accordion>

  <Accordion title="Can I change the webhook URL?">
    No, webhook URLs cannot be changed after creation for security reasons. To use a different URL, delete the existing webhook and create a new one.
  </Accordion>

  <Accordion title="What happens if my endpoint is down?">
    AutoSend retries failed deliveries up to 3 times with exponential backoff. After all retries are exhausted, the delivery is marked as failed. See the <a href={APP_PATHS.webhookRetries} title="Retries and Replays">Retries and Replays</a> guide for details.
  </Accordion>

  <Accordion title="Is there a request timeout?">
    Yes, webhook requests have a 10-second timeout. Make sure your endpoint responds within this window.
  </Accordion>

  <Accordion title="How do I test webhooks locally?">
    Use tools like <a href={NGROK_LINK} target="_blank" title="ngrok">ngrok</a> or <a href={LOCALTUNNEL_LINK} target="_blank" title="localtunnel">localtunnel</a> to expose your local server to the internet. Create a webhook in AutoSend with your tunnel URL, then trigger events by sending emails or managing contacts.
  </Accordion>

  <Accordion title="Are webhooks secure?">
    Yes, all webhooks are sent over HTTPS and include an HMAC-SHA256 signature for verification. See <a href={APP_PATHS.webhookVerifyRequests} title="Verify Webhook Requests">Verify Webhook Requests</a> for implementation details.
  </Accordion>
</AccordionGroup>

***

## Related resources

<Columns cols={2}>
  <Card title="Event Types" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/event-types.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=7e37cf26e7cf6375a72cf73483295f3f" href={APP_PATHS.webhookEventType} width="24" height="24" data-path="icons/event-types.svg">
    Complete list of webhook events and payloads
  </Card>

  <Card title="Retries and Replays" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/retries-refresh.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=6c8bfc5edaa01d3624986eebbb4cbb09" href={APP_PATHS.webhookRetries} width="24" height="24" data-path="icons/retries-refresh.svg">
    Automatic retry logic and best practices
  </Card>

  <Card title="Verify Webhook Requests" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/verify-requests.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=dec233ef8a7a82b41b8ab4d6540904ec" href={APP_PATHS.webhookVerifyRequests} width="24" height="24" data-path="icons/verify-requests.svg">
    Security and signature verification
  </Card>

  <Card title="Webhooks" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/webhook.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=14ad6675c71731ac04f786559a813ee1" href={AUTOSEND_PATHS.webhooks} width="24" height="24" data-path="icons/webhook.svg">
    Manage your webhooks from the AutoSend sidebar
  </Card>
</Columns>
