---
title: "Webhooks | Prontuno"
description: "Receive email delivery events in your application so you can update records, investigate failures, and respond to bounces as they happen."
canonical: https://prontuno.com/features/webhooks/
---

# Know what happened after you hit send.

**Webhooks · Signed JSON POST**

Receive email delivery events in your application so you can update records, investigate failures, and respond to bounces as they happen.

[Request access](https://app.prontuno.com/get-started/) · [Explore the API reference](https://prontuno.com/api-reference/)

## Webhook deliveries

### Example delivery log

Endpoint: `POST https://example.com/webhooks/prontuno`

| Event | Recipient | Response | Time |
| --- | --- | --- | --- |
| `email.delivered` | `ada@example.com` | 200 | 09:14:05 |
| `email.sent` | `grace@example.com` | 200 | 09:14:04 |
| `email.bounced` | `alan@example.org` | 200 | 09:14:02 |
| `email.deferred` | `mary@example.net` | 503, retrying | 09:13:58 |
| `email.delivered` | `linus@example.com` | 200 | 09:13:51 |

Requests include a `Prontuno-Signature` header.

Each request is signed. Failed deliveries are retried automatically.

## Turn delivery signals into action

Instead of polling for the status of each email, your application is told when something changes, so records stay current and failures surface quickly.

### Only the events you need

Each endpoint subscribes only to the events it acts on, so every system receives just what it needs.

### Scope each endpoint

Receive events for your whole team, or only for one verified sending domain.

### Manage by API or dashboard

Create, update, and delete endpoints and rotate signing secrets through the API or your Prontuno account.

## Events you can trust, processed once

An endpoint on the public internet can receive forged, repeated, or missed requests. Signatures, event IDs, and retries address each of these.

### Delivery events

| Event | Sent when |
| --- | --- |
| email.sent | Prontuno accepted the message and passed it to its mail servers. |
| email.delivered | The recipient’s mail server accepted the message. |
| email.deferred | The receiving server asked for a later retry. Prontuno retries automatically. |
| email.bounced | The receiving server refused the message. |
| email.rejected | Prontuno did not send the message, for example because the recipient is on the suppression list. |
| email.expired | Delivery kept being retried until the retry period ran out without success. |
| email.complained | The recipient reported the message as spam to their mailbox provider. Prontuno suppresses the address automatically. |

### HMAC-SHA256 signatures

The Prontuno-Signature header is computed from the timestamp and raw body with your endpoint secret. A request without a valid signature did not come from Prontuno.

### Unique event IDs

Each event carries an ID, in the body and in the Prontuno-Event-Id header, so a repeated delivery can be recognized.

### Automatic retries

Failed deliveries are retried with increasing delays, so a brief outage does not lose events.

### Bounce details included

Bounce events include the bounce type, the SMTP status code, and the receiving server’s diagnostic message.

### Receive an event examples

#### Payload

```json
{
  "event_id": "evt_01kxx24h94e00v5rgamqhe607z",
  "webhook_id": "whk_01kxjf1tyje00bgxb0s9g4gfxn",
  "type": "email.bounced",
  "created_at": "2026-09-25T09:14:03.120Z",
  "data": {
    "email_id": "email_01kxwz4bfhe00rr3ww02remnap",
    "to_email": "ada@example.com",
    "subject": "Your receipt",
    "bounced_at": "2026-09-25T09:14:02.884Z",
    "reason": "Mailbox does not exist",
    "bounce": {
      "type": "permanent",
      "enhanced_status_code": "5.1.1"
    }
  }
}
```

#### Verify in Node.js

```js
import crypto from 'node:crypto';

function isValidSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map((part) => part.split('=')),
  );
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');

  return (
    typeof parts.v1 === 'string' &&
    parts.v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
  );
}
```

#### Verify in PHP

```php
$payload = file_get_contents('php://input');
$parts = [];

foreach (explode(',', $_SERVER['HTTP_PRONTUNO_SIGNATURE'] ?? '') as $part) {
    [$key, $value] = array_pad(explode('=', $part, 2), 2, '');
    $parts[$key] = $value;
}

$expected = hash_hmac('sha256', ($parts['t'] ?? '') . '.' . $payload, $secret);

if (! hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(401);
    exit;
}
```

## Receive delivery events

1. Add a public HTTPS endpoint to your application.
2. Create a webhook in Prontuno, choose its events, and store the signing secret.
3. Verify each signature and skip event IDs you have already processed.

[Explore the API reference](https://prontuno.com/api-reference/)

## Explore more of Prontuno

- [Email API](https://prontuno.com/features/email-api/) — Send email with one HTTP request.
- [SMTP relay](https://prontuno.com/features/smtp-relay/) — Connect existing apps over SMTP.
- [Domain authentication](https://prontuno.com/features/domain-authentication/) — Set up SPF, DKIM, and DMARC.
- [Dedicated IP](https://prontuno.com/features/dedicated-ip/) — An IP reserved for your team.

## Request access

Ready to send with Prontuno? Request access and tell us about your sending needs. Applications are reviewed within 24 hours.

[Request access](https://app.prontuno.com/get-started/)
