What is a Webhook

Webhook is a mechanism that allows your system to receive automatic notifications about events via HTTP requests. When a certain event occurs in our system (for example, a campaign is completed), we send a POST request with detailed information to the URL you specify.

Webhook Setup

You can configure webhooks in the web control panel:

  1. Go to the Integrations section.
  2. Click on the Webhooks card.
  3. Click Create webhook.
  4. Fill in the fields:
    • Webhook Name — for your personal identification of the webhook.
    • Webhook URL — the address of your server that will receive notifications.
    • Events — select from the list the events you want to be notified about.
  5. Click Create Webhook.

After creation, the webhook will appear in the general list. Its card displays all key information:

  • Status (Active / Inactive)
  • Webhook Name and Webhook URL
  • List of tracked events
  • Time of last successful send
  • Activity (total number of events sent)

The following actions are available in the menu (⋮) in the corner of the card:

  • Test
  • Edit
  • Activate / Deactivate
  • Delete

Notification Format

{
  "eventId": "<uuid>",
  "trigger": "<entity>.<event>",
  "occurredAt": "2023-10-27T12:35:01.123Z",
  "payload": {
    "someStringField": "string",
    "numberField": 42
  }
}

Request fields:

  • eventId — unique event identifier.
  • trigger — event type in entity.event format (see list below).
  • occurredAt — time the event occurred in ISO 8601 format (UTC).
  • payload — object with data specific to each event.

Test Event

When you click the Test button in the menu (⋮) in the corner of the card, you will receive a POST request with the following payload:

{
  "eventId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "trigger": "emailmassivo.webhook_test",
  "occurredAt": "2030-12-31T20:59:59.999Z",
  "payload": {
    "timestamp": "2030-12-31T20:59:59.999Z",
    "stringField": "someString",
    "numberField": 42,
    "booleanField": true,
    "objectField": {
      "nestedString": "value",
      "nestedNumber": 123
    },
    "arrayField": ["item1", "item2", 3, false]
  }
}

Available Triggers

Entity: Email campaigns (mail_distribution)

Common Payload for campaign events

For events mail_distribution.started, mail_distribution.completed, mail_distribution.moderating, mail_distribution.approved and mail_distribution.rejected, the following payload structure is used:

{
  "mailDistributionId": 12345,
  "mailDistributionName": "Campaign name",
  "mailDistributionSubject": "Email subject"
}
  • mail_distribution.started — campaign has been started (Started).
  • mail_distribution.completed — campaign completed successfully (Completed).
  • mail_distribution.moderating — campaign sent for review (Under Moderation).
  • mail_distribution.approved — campaign approved by moderator (Approved).
  • mail_distribution.rejected — campaign rejected by moderator (Rejected).
  • mail_distribution.banned — campaign is blocked (Stopped by System). This event has an extended payload.

Payload:

{
  "mailDistributionId": 12345,
  "mailDistributionName": "Campaign name",
  "mailDistributionSubject": "Email subject",
  "negative": {
    "reason": "hard_bounced",
    "explanation": "Detailed description of the block reason"
  }
}

The reason field will contain one of the following strings:

  • hard_bounced
  • soft_bounced
  • error_spam
  • complain

Processing Requirements

1. Server response and timeout

Your server must respond to the request within 30 seconds.

  • HTTP status 200299: means the event was successfully received and processed.
  • Any other status (3xx, 4xx, 5xx) or timeout: is considered an error, after which we will attempt to deliver the event again.

2. Idempotency

Our system guarantees delivery on an at-least-once basis. This means that due to network issues or errors on your server side, the same event may be delivered more than once. Use the eventId field to prevent duplicate processing.

3. Retries

In case of delivery failure we will make 6 retry attempts with increasing intervals.

Attempt Delay after previous
1 1 minute
2 2 minutes
3 4 minutes
4 8 minutes
5 15 minutes
6 15 minutes

In total we may send up to 7 requests for one event (initial send + 6 retries). After the last failed attempt we will stop delivering the event and it will be marked as undelivered.

4. URL requirements

Your URL handler must use the HTTPS protocol and have a valid SSL certificate. Notifications to HTTP addresses will not be delivered.

Handler Example

PHP

<?php
// Get raw request body
$input = file_get_contents('php://input');
$data = json_decode($input, true);

if (!$data || !isset($data['eventId'])) {
    http_response_code(400); // Bad Request
    exit('Invalid payload');
}

// Check if event was already processed
if (isEventProcessed($data['eventId'])) {
    http_response_code(200);
    exit('OK. Already processed.');
}

// Process the event
switch ($data['trigger']) {
    case 'mail_distribution.completed':
        handleMailDistributionCompleted($data['payload']);
        break;
    case 'mail_distribution.banned':
        handleMailDistributionBanned($data['payload']);
        break;
    // ... other events
}

// Mark event as processed
markEventAsProcessed($data['eventId']);

http_response_code(200);
echo 'OK';
?>

Node.js (Express)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  const { eventId, trigger, payload } = req.body;

  // Check idempotency
  if (isEventProcessed(eventId)) {
    return res.status(200).send('OK. Already processed.');
  }

  // Process the event
  switch (trigger) {
    case 'mail_distribution.completed':
      handleMailDistributionCompleted(payload);
      break;
    case 'mail_distribution.banned':
      handleMailDistributionBanned(payload);
      break;
    // ... other events
  }

  // Mark as processed
  markEventAsProcessed(eventId);

  res.status(200).send('OK');
});

Development Recommendations

1. Process requests asynchronously

Your server should respond with HTTP 200 OK as quickly as possible. Do not perform long-running operations (calls to other APIs, file processing) at the moment of receiving the webhook. Instead, add the task to a queue (e.g. RabbitMQ, Redis, SQS) and return the response immediately. This will protect you from timeouts and make it easy to scale processing.

2. Be prepared for new fields

New fields may be added to the payload object in the future. Your code should be resilient to this and not fail if unknown keys appear in the JSON.

3. Set up monitoring

Set up monitoring for your URL handler. Alerts will help you react to issues quickly and fix them before events start being lost after all retry attempts.

4. Use test environments

Do not use your main (production) URL for debugging. Create a separate webhook for a test or staging environment to safely make changes to the code.

Frequently Asked Questions (FAQ)

  • I've set up a webhook but I'm not receiving notifications. What should I do?

    Check the following:

    1. Status in UI: Make sure the webhook is Active.
    2. Test event: Use the Test button on the webhook card.
    3. Public URL: Make sure the URL is accessible from the internet (not localhost).
    4. HTTPS: Make sure the URL starts with https:// and has a valid SSL certificate.
    5. Server logs: Check your web server (Nginx, Apache) or application logs for incoming request entries.
  • Why do you send the same event (eventId) multiple times?

    We repeat the send if we do not receive confirmation of successful delivery from your server (response with HTTP code 200–299) within 30 seconds. This can happen for two main reasons:

    1. Your server returned an error (e.g. status 500 Internal Server Error).
    2. A network issue occurred, due to which we did not receive the response, even if your application sent it and successfully processed the event.

    Therefore your system should always be ready for duplicates (see the Idempotency section).

  • Is the order of event delivery guaranteed?

    No, it is not. Due to network behaviour or retry logic, an event that occurred later may be delivered earlier. Always rely on the occurredAt timestamp in the request body, not on the order in which they are received.

  • What happens if my server is unavailable for a long time?

    We will attempt to deliver the event for approximately 45 minutes. If your server remains unavailable longer, the event will be lost. Plan maintenance with this window in mind.

  • Can I use the same URL for multiple webhooks?

    Yes. This is common practice. In your code use the trigger field for routing and calling the appropriate processing logic.