# Guide to SMTP2GO Webhooks: Features and Best Practices

SMTP2GO webhooks tell your application what happens to the email and SMS you send through SMTP2GO. An email is delivered, opened or clicked, bounces, is marked as spam, or a recipient unsubscribes, and SMTP2GO calls your endpoint, so you can update contact records, suppress bad addresses and track engagement as it happens.

This guide covers how SMTP2GO webhooks are set up, the events and parameters they send, how to authenticate them with the Authorization header, how retries work, and the practices that keep a receiver reliable.

## What are SMTP2GO webhooks?

An SMTP2GO webhook is an HTTP `POST` to your URL for each email or SMS event you select. You decide which senders it applies to (SMTP users, API keys or authenticated IPs), which events it reports, and whether the body is **JSON** or **Form encoded**.

SMTP2GO doesn't sign webhooks. Each webhook can have an optional **Authorization header** of type bearer or basic, which SMTP2GO sends with every request. SMTP2GO's overview also mentions other ways to protect the URL: credentials embedded in the URL, a password query parameter, a hard-to-guess path, or checking that requests come from the addresses behind `webhooks.smtp2go.com`. The Authorization header is the cleanest of these, and the one Hookdeck checks.

## SMTP2GO webhook features

| Feature | Details |
| --- | --- |
| Configuration | SMTP2GO App, **Settings** > **Webhooks** > **Manage Webhooks**, or the API |
| Limit | 1 webhook on the free plan, up to 10 on paid plans |
| Scope | Selected SMTP users, API keys or authenticated IPs |
| Method | `POST` |
| Body format | JSON or form-encoded, chosen per webhook. The API defaults to form |
| Event field | `event` |
| Authentication | Optional Authorization header, bearer or basic |
| Signature and timestamp header | None |
| Timeout | 10 seconds, measured until response headers arrive |
| Retries | Up to 35 times over 48 hours |
| Failed notifications | Viewable, retryable and cancellable in the App |
| Custom email headers | Can be added to the event data |
| Source IPs | No fixed list. SMTP2GO suggests the A record of `webhooks.smtp2go.com` |

## Events and parameters

| Email `event` | Meaning |
| --- | --- |
| `processed` | Received by SMTP2GO and being processed for delivery |
| `delivered` | The recipient server accepted it with a `250` response |
| `open` | Opened. Can fire on each open |
| `click` | A tracked link was clicked. Can fire on each click |
| `bounce` | Bounced. The `bounce` parameter says `hard` or `soft` |
| `spam` | Marked as spam. The address is added to your suppressions |
| `unsubscribe` | Unsubscribed through the unsubscribe footer. The address is added to your suppressions |
| `resubscribe` | Resubscribed, or removed from the suppressions page |
| `reject` | Rejected: a suppressed address, an unverified sender, or a sandboxed sender |

SMS events cover messages that are sending, submitted, delivered, failed or rejected, plus recipient opt-outs. The `event` values SMTP2GO documents for SMS are `sms_sending`, `sms_submitted`, `sms_delivered`, `sms_failed` and `sms_rejected`.

Email events carry a flat set of parameters, including `event`, `time`, `sendtime`, `sender`, `from`, `rcpt`, `recipients`, `subject`, `email_id`, `id` (the webhook's unique ID), `message-id`, `auth` (the SMTP user, the first 16 characters of the API key, or the IP that sent the email), and, for opens and clicks, `user-agent`, `client`, `client-device`, `client-os` and `geoip-*` fields. SMS events carry fields such as `message_id`, `destination_number`, `source_number`, `status_code` and `received_timestamp`.

Some names contain hyphens (`message-id`, `read-secs`, `geoip-country`), so read them with bracket notation rather than attribute access.

> **Inspect SMTP2GO webhooks as they arrive.** Use a [Hookdeck Inspect](https://inspect.hookdeck.com) URL as the webhook URL and click **Test this webhook** to see the exact headers and body for your output type, with no account or setup required.

## Setting up SMTP2GO webhooks

Generate a long random token first, for example with `openssl rand -hex 32`.

1. In the SMTP2GO App, go to **Settings** > **Webhooks** > **Manage Webhooks** and click **Add Webhook**.
2. Enter your HTTPS endpoint as the **URL**.
3. In **Authorization header**, choose bearer and enter your token.
4. Under **Users**, select the SMTP users, API keys or authenticated IPs the webhook applies to.
5. Choose the **Output type**: JSON or Form encoded.
6. Select the email events and SMS events to include, and any custom email headers you want reported.
7. Click **Save changes**, then **Test this webhook**.

Through the API, `webhook/add` takes the same settings, with `auth_header_type` (`bearer` or `basic`) and `auth_header_value` (a custom token, or `base64(user:pass)` for basic). Set `output_format` to `json` explicitly if you want JSON, since the API defaults to form.

**With Hookdeck.** SMTP2GO is a supported source in Hookdeck. Its setup note reads: "Set an Authorization header on the SMTP2GO webhook and paste the full value, including the "Bearer " or "Basic " prefix." Create a source with the **SMTP2GO** type and use its URL as the webhook URL. In **Authorization Header**, paste the whole header value, for example `Bearer your-long-random-token`. SMTP2GO's docs don't print the outgoing header, so check the first test delivery in Hookdeck and make sure the value matches what arrives. The source accepts `POST` only, which is what SMTP2GO sends. A request with a missing or wrong value still gets a `200` from Hookdeck, but Hookdeck marks it as failed verification and doesn't deliver it to your service.

For local development, `hookdeck listen 3000 smtp2go --path /webhooks/smtp2go` gives you a public URL that forwards to your machine.

## Securing SMTP2GO webhooks

Compare the whole `Authorization` header with the value you expect, prefix included, in constant time. Then parse the body as JSON or form data, depending on its content type.

```javascript
const crypto = require('crypto');
const express = require('express');

// The full value, e.g. "Bearer your-long-random-token".
const EXPECTED = process.env.SMTP2GO_AUTHORIZATION;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

function parseBody(req) {
  const text = req.body.toString('utf8');
  if ((req.get('content-type') || '').includes('application/json')) {
    return JSON.parse(text);
  }
  return Object.fromEntries(new URLSearchParams(text)); // Form encoded
}

const app = express();

app.post('/webhooks/smtp2go', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get('authorization');
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  let event;
  try {
    event = parseBody(req);
  } catch {
    return res.sendStatus(400);
  }

  if (event.event === 'bounce' && event.bounce === 'hard') {
    // Suppress event.rcpt in your own system.
  }

  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

EXPECTED = os.environ.get("SMTP2GO_AUTHORIZATION", "")  # e.g. "Bearer your-token"

def verify_smtp2go_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get("Authorization")
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

```

A static Authorization value authenticates the sender, not the body, so a modified payload still passes, and nothing stops a captured request from being replayed. Use HTTPS and a long random token. Avoid the URL-based options from SMTP2GO's overview where you can: credentials in a URL or query string end up in access logs.

> **Make SMTP2GO webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks the Authorization header, answers within SMTP2GO's 10-second timeout, and absorbs bursts of open and click events before they reach your service.

## SMTP2GO webhook limitations and pain points

### Opens and clicks come in bursts

**The Problem:** A large campaign produces a flood of `open` and `click` events that can overload a small receiver.

**Why It Happens:** Each open and each click can trigger its own event, and SMTP2GO sends them as they happen.

**Workarounds:**

* Only subscribe to engagement events if you use them.
* Queue events in the handler and process them at a steady rate.

**How Hookdeck Can Help:** Hookdeck accepts the burst and delivers to your service at a [max delivery rate](/docs/destinations#set-a-max-delivery-rate) you set.

### Two body formats

**The Problem:** A handler written for JSON fails on form-encoded bodies, or the other way round.

**Why It Happens:** The output type is a per-webhook setting, and the API defaults to form encoding even though the App lets you pick JSON.

**Workarounds:**

* Set the output type explicitly and parse by `Content-Type`, as in the example above.

**How Hookdeck Can Help:** Hookdeck shows each request's content type and body, so you can confirm the format before you deploy a parser.

### The outgoing header isn't documented

**The Problem:** The value you compare against may not match what SMTP2GO sends.

**Why It Happens:** SMTP2GO documents the bearer and basic options and the API fields, but not the exact header string it builds from them.

**Workarounds:**

* Capture a test delivery and copy the `Authorization` value exactly.
* Prefer bearer over basic, so you're comparing one random token rather than an encoded username and password.

**How Hookdeck Can Help:** Hookdeck keeps failed-verification requests with their headers, so you can compare the received value with the one on the source.

### Long retries, then cancellation

**The Problem:** A broken handler gets the same events again for two days.

**Why It Happens:** SMTP2GO retries up to 35 times over 48 hours: 5 times in the first 30 minutes, hourly for 24 hours, every 6 hours for the next 24, every 12 hours for the next 24, and once more after that.

**Workarounds:**

* Make handlers idempotent so repeats do no harm.
* Use **Settings** > **Webhooks** > **Failed Notifications** to retry or cancel failed attempts after a fix.

**How Hookdeck Can Help:** Hookdeck acknowledges SMTP2GO straight away, so its retry schedule doesn't run, and you [retry](/docs/retries) delivery to your service on your own terms.

## Best practices

### Deduplicate on `id`

SMTP2GO describes `id` as the unique ID for the webhook. Store it and skip repeats, since retries resend the same event. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Act on hard bounces and spam complaints

`bounce` with `hard`, `spam` and `unsubscribe` mean you should stop mailing that address. SMTP2GO already suppresses spam and unsubscribe addresses on its side; mirror them in your own list so other senders respect them too.

### Report custom headers you need for correlation

Add a custom email header, such as your own message or campaign ID, and select it in the webhook's email headers. It comes back with each event, so you can match events to your records.

### Acknowledge fast and process asynchronously

SMTP2GO times out after 10 seconds without response headers. Return `200` once the header checks out and the body is queued. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

SMTP2GO webhooks report email and SMS events as JSON or form-encoded `POST` requests, with an optional bearer or basic Authorization header and no signature. Set a bearer token, compare the full header in constant time, parse by content type, deduplicate on `id`, and answer within 10 seconds so you aren't relying on 48 hours of retries.

[Hookdeck Event Gateway](https://hookdeck.com) checks the header, absorbs engagement bursts, and retries and replays delivery to your service.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle SMTP2GO webhooks reliably in minutes.