Email Forwards API
Forward every email an inbox receives to a webhook URL, as signed JSON. For the payload, the signature and how to test a handler locally, see Forward email to webhooks.
Forwards need a paid plan. The plan's forwarding destinations limit caps forwards per inbox: Pro 3, Production and Scale unlimited. All routes use the usual authentication.
| Route | Result |
|---|---|
GET /api/v1/inboxes/:id/forwards | List an inbox's forwards |
POST /api/v1/inboxes/:id/forwards | Create a forward |
GET /api/v1/inboxes/:id/forwards/:forward_id | Get a forward |
PATCH /api/v1/inboxes/:id/forwards/:forward_id | Update a forward |
DELETE /api/v1/inboxes/:id/forwards/:forward_id | Delete a forward and its delivery log |
GET /api/v1/inboxes/:id/forwards/:forward_id/deliveries | List a forward's latest deliveries |
POST /api/v1/inboxes/:id/forwards/:forward_id/test | Send a test delivery |
POST /api/v1/inboxes/:id/emails/:email_id/redeliver | Deliver a stored email again |
The forward object
{
"id": "8f0c2a6e-1d4b-4e7a-9c3f-2b1a0d9e8c7f",
"inbox_id": "5c1d0e9a-3f7b-4f0e-9c1a-1b2e3f4a5b6c",
"url": "https://example.com/inbound-email",
"enabled": true,
"filters": { "to": null, "from": null, "subject_contains": "Re:" },
"signing_secret": "whsec_MfKQ9r8GKYqrTwJnYk3jYQ2JmLz7rK1V",
"created_at": "2026-10-12T10:00:00Z",
"updated_at": "2026-10-12T10:00:00Z"
}
signing_secret signs every delivery (see Verify the signature). It's left out for read-only API keys.
List forwards
GET /api/v1/inboxes/:id/forwards
Returns {"data": [forward, …]}, oldest first.
Create a forward
POST /api/v1/inboxes/:id/forwards
curl -X POST https://app.hooklistener.com/api/v1/inboxes/INBOX_ID/forwards \
-H "Authorization: Bearer hklst_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/inbound-email",
"filters": {"to": "[email protected]"}
}'
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Public http(s) URL that receives each email. Private and local addresses are refused |
enabled | boolean | No | Whether the forward delivers email. Default true |
filters.to | string | No | Only emails sent to this exact address, case-insensitive |
filters.from | string | No | Only emails whose From contains this text, case-insensitive |
filters.subject_contains | string | No | Only emails whose subject contains this text, case-insensitive |
When several filters are set, all must match. Filters can also be sent as top-level to, from and subject_contains fields, and the body can be wrapped as {"forward": {…}}.
Returns 201 with {"data": forward}, including its signing_secret, and a Location header.
Errors:
| Status | When |
|---|---|
403 | Your plan doesn't include forwarding, or the inbox already has the plan's number of forwards. The body includes error and an upgrade object |
422 | Invalid attributes, e.g. a URL that isn't public |
Get a forward
GET /api/v1/inboxes/:id/forwards/:forward_id
Returns {"data": forward}.
Update a forward
PATCH /api/v1/inboxes/:id/forwards/:forward_id
# Pause a forward
curl -X PATCH https://app.hooklistener.com/api/v1/inboxes/INBOX_ID/forwards/FORWARD_ID \
-H "Authorization: Bearer hklst_your_api_key" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
Takes the same fields as Create a forward. Updates are partial: fields you leave out keep their value, and null or "" clears a filter. The signing secret doesn't change.
Returns {"data": forward}.
Delete a forward
DELETE /api/v1/inboxes/:id/forwards/:forward_id
Deletes the forward and its delivery log. Returns 204 No Content.
List deliveries
GET /api/v1/inboxes/:id/forwards/:forward_id/deliveries
Latest first. limit sets how many (default 20, max 100).
{
"data": [
{
"id": "3e9b7c1d-5a2f-4d8e-b6c0-9f1a2e3d4c5b",
"forward_id": "8f0c2a6e-1d4b-4e7a-9c3f-2b1a0d9e8c7f",
"email_id": "b72d4c1e-6a0f-4a51-9f3e-2d8c1b7e9a10",
"webhook_id": "msg_b72d4c1e-6a0f-4a51-9f3e-2d8c1b7e9a10",
"trigger": "email",
"status": "succeeded",
"attempts": 2,
"status_code": 200,
"duration_ms": 143,
"error": null,
"response_snippet": "{\"ok\":true}",
"attempt_log": [
{ "attempt": 1, "status_code": 502, "duration_ms": 87, "error": "HTTP 502", "at": "2026-10-12T10:02:12Z" },
{ "attempt": 2, "status_code": 200, "duration_ms": 143, "error": null, "at": "2026-10-12T10:02:27Z" }
],
"last_attempt_at": "2026-10-12T10:02:27Z",
"completed_at": "2026-10-12T10:02:27Z",
"created_at": "2026-10-12T10:02:11Z"
}
]
}
| Field | Description |
|---|---|
trigger | email (the automatic delivery), redeliver or test |
status | pending, retrying, succeeded, failed, or skipped (the forward was paused before a queued delivery ran) |
email_id | The forwarded email, or null for a test with the sample email |
webhook_id | The webhook-id header sent; the same for every delivery of one email, msg_test_… for tests |
response_snippet | First 1 KB of your endpoint's answer |
attempt_log | Every attempt with its status code, duration and error |
The automatic delivery is retried with exponential backoff for up to 8 attempts (about 30 minutes); redeliveries and tests make one attempt. Deliveries of an email are deleted with the email when it leaves your plan's retention window.
Send a test
POST /api/v1/inboxes/:id/forwards/:forward_id/test
Sends the inbox's latest email to the forward, or a sample email when the inbox is empty, in one attempt. Works on a paused forward. Returns 202 Accepted with {"data": delivery}; poll the deliveries for the result.
Redeliver an email
POST /api/v1/inboxes/:id/emails/:email_id/redeliver
curl -X POST https://app.hooklistener.com/api/v1/inboxes/INBOX_ID/emails/EMAIL_ID/redeliver \
-H "Authorization: Bearer hklst_your_api_key" \
-H "Content-Type: application/json" \
-d '{"forward_id": "FORWARD_ID"}'
| Field | Required | Description |
|---|---|---|
forward_id | No | Deliver to this forward only, even if it's paused or its filters don't match. Without it, the email goes to every enabled forward whose filters match |
Each redelivery makes one attempt and reuses the email's webhook-id, so receivers that deduplicate on it ignore emails they already processed. Returns 202 Accepted with {"data": [delivery, …]}.
Errors:
| Status | When |
|---|---|
400 | No forward_id, and no enabled forward of the inbox matches the email |
403 | Your plan doesn't include forwarding |
404 | Unknown inbox, email or forward |