Webhooks.
Create and manage the URLs Notix sends events to, send a test event, and read and retry the call log. Every request needs an API key in the Authorization header; the reference overview covers keys, errors and rate limits.
List webhooks
GET/v1/webhooksFull access key
Every webhook of the team, newest first, with a summary of its last 24 hours: calls delivered (ok), extra attempts it needed (retried) and calls that ran out of attempts (failed). The signing secret is never shown here: secretHint is always masked. A read only key can call it; a key limited to one domain cannot.
curl -X GET "https://app.usenotix.dev/api/v1/webhooks" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.list();
data?.data.forEach((webhook) => console.log(webhook.url, webhook.status, webhook.last24h));
Responses
200Every webhook of the team, newest first.
| Field | Type | About |
|---|---|---|
datarequired | object[] | |
data[].idrequired | string | |
data[].urlrequired | string | |
data[].descriptionrequired | string | Can be null. |
data[].statusrequired | string | One of |
data[].eventTypesrequired | string[] | |
data[].domainIdsrequired | integer[] | |
data[].apiVersionrequired | string | Can be null. |
data[].consecutiveFailuresrequired | integer | |
data[].lastFailureAtrequired | string | Can be null. |
data[].lastSuccessAtrequired | string | Can be null. |
data[].secretHintrequired | string | Always masked. The signing secret is returned once, when the webhook is created or its secret is rotated. |
data[].createdAtrequired | string | |
data[].updatedAtrequired | string | |
data[].last24hrequired | object | |
data[].last24h.okrequired | integer | Calls delivered in the last 24 hours. |
data[].last24h.retriedrequired | integer | Delivery attempts beyond each call's first. |
data[].last24h.failedrequired | integer | Calls that ran out of attempts or were dropped. |
403`SCOPE_DENIED`: a send-only key or a live AI key. `DOMAIN_PINNED`: the key is limited to one domain, and webhooks carry every domain's events.
Create a webhook
POST/v1/webhooksFull access key
Start sending events to a URL. Pick the events in eventTypes (an empty list sends every event) and, optionally, limit them to some of your domains with domainIds.
The response is the only time Notix shows the signing secret (secret). Store it now and use it to check the X-Notix-Signature header of every event. If you lose it, rotate it.
Your plan sets how many webhooks a team can have; past that the answer is 403. A team can create 20 webhooks a minute.
The URL
Notix sends events from its own network, so the URL must be https with a public hostname. A raw IP address, localhost, a private or internal address, or a hostname that does not resolve answers 400 BAD_REQUEST. Notix checks the address again before every delivery.
Who can call it
A full access key created by a team admin, as on the dashboard. Every refusal is 403:
| Code | Why |
|---|---|
SCOPE_DENIED | The key is not a full access key (send-only, read-only, sandbox-only or live AI). |
DOMAIN_PINNED | The key is limited to one domain. A webhook receives the events of every domain. |
FORBIDDEN | The key was made by a team member without admin rights, or before Notix recorded who made each key. Create a new key as a team admin. |
Adding a webhook, pointing one at a new URL or removing one emails the team's owner and admins, naming the key.
| Field | Type | About |
|---|---|---|
urlrequired | string (uri) | Where Notix sends events. Must be https with a public hostname: private addresses, localhost and raw IP addresses are refused. At most 2,048 characters. |
eventTypesrequired | string[] | The events to send. An empty list means every event. |
description | string | At most 500 characters. |
domainIds | integer[] | Only send events for these domains. An empty list means every domain. |
curl -X POST "https://app.usenotix.dev/api/v1/webhooks" \
-H "Authorization: Bearer $NOTIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/notix",
"eventTypes": [
"email.delivered",
"email.bounced"
],
"description": "Order system"
}'
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.create({
url: "https://example.com/webhooks/notix",
eventTypes: ["email.delivered", "email.bounced"],
description: "Order system",
});
// Shown only once: store it with your app's settings.
const signingSecret = data?.secret;
Responses
201The webhook was created. `secret` is shown only here.
| Field | Type | About |
|---|---|---|
idrequired | string | |
urlrequired | string | |
descriptionrequired | string | Can be null. |
statusrequired | string | One of |
eventTypesrequired | string[] | |
domainIdsrequired | integer[] | |
apiVersionrequired | string | Can be null. |
consecutiveFailuresrequired | integer | |
lastFailureAtrequired | string | Can be null. |
lastSuccessAtrequired | string | Can be null. |
secretHintrequired | string | Always masked. The signing secret is returned once, when the webhook is created or its secret is rotated. |
createdAtrequired | string | |
updatedAtrequired | string | |
secretrequired | string | The signing secret. Shown only in this response: store it now. Notix never shows it again. |
400The URL is not a public https URL, or the body is not valid.
403`SCOPE_DENIED`: the key is not a full access key (send-only, read-only, sandbox-only or live AI). `DOMAIN_PINNED`: the key is limited to one domain. `FORBIDDEN`: the key was made by a team member without admin rights, or before Notix recorded who made each key (create a new key). `FORBIDDEN` also when the team's plan allows no more webhooks.
404One of `domainIds` is not a domain of the team.
429The team created 20 webhooks in the last minute.
Get a webhook
GET/v1/webhooks/{webhookId}Full access key
One webhook: its URL, events, domains, status and failure count. status is ACTIVE, PAUSED, or AUTO_DISABLED when Notix stopped it after repeated failures. The signing secret is masked.
| Field | Type | About |
|---|---|---|
webhookIdrequired | string |
curl -X GET "https://app.usenotix.dev/api/v1/webhooks/<webhookId>" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.get("wh_123");
Responses
200The webhook. Its signing secret is masked.
| Field | Type | About |
|---|---|---|
idrequired | string | |
urlrequired | string | |
descriptionrequired | string | Can be null. |
statusrequired | string | One of |
eventTypesrequired | string[] | |
domainIdsrequired | integer[] | |
apiVersionrequired | string | Can be null. |
consecutiveFailuresrequired | integer | |
lastFailureAtrequired | string | Can be null. |
lastSuccessAtrequired | string | Can be null. |
secretHintrequired | string | Always masked. The signing secret is returned once, when the webhook is created or its secret is rotated. |
createdAtrequired | string | |
updatedAtrequired | string |
403`SCOPE_DENIED`: a send-only key or a live AI key. `DOMAIN_PINNED`: the key is limited to one domain, and webhooks carry every domain's events.
404No webhook with this id for the calling team.
Update a webhook
PATCH/v1/webhooks/{webhookId}Full access key
Change the URL, events, description or domains. Send only the fields you want to change; description: null clears it. The signing secret does not change.
The URL
Notix sends events from its own network, so the URL must be https with a public hostname. A raw IP address, localhost, a private or internal address, or a hostname that does not resolve answers 400 BAD_REQUEST. Notix checks the address again before every delivery.
Who can call it
A full access key created by a team admin, as on the dashboard. Every refusal is 403:
| Code | Why |
|---|---|
SCOPE_DENIED | The key is not a full access key (send-only, read-only, sandbox-only or live AI). |
DOMAIN_PINNED | The key is limited to one domain. A webhook receives the events of every domain. |
FORBIDDEN | The key was made by a team member without admin rights, or before Notix recorded who made each key. Create a new key as a team admin. |
Adding a webhook, pointing one at a new URL or removing one emails the team's owner and admins, naming the key.
| Field | Type | About |
|---|---|---|
webhookIdrequired | string |
| Field | Type | About |
|---|---|---|
url | string (uri) | Where Notix sends events. Must be https with a public hostname: private addresses, localhost and raw IP addresses are refused. At most 2,048 characters. |
eventTypes | string[] | The events to send. An empty list means every event. |
description | string | At most 500 characters. Can be null. |
domainIds | integer[] | Only send events for these domains. An empty list means every domain. |
curl -X PATCH "https://app.usenotix.dev/api/v1/webhooks/<webhookId>" \
-H "Authorization: Bearer $NOTIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"eventTypes": [
"email.delivered",
"email.bounced",
"email.complained"
]
}'
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.update("wh_123", {
eventTypes: ["email.delivered", "email.bounced", "email.complained"],
});
Responses
200The updated webhook. Its signing secret is unchanged and masked.
| Field | Type | About |
|---|---|---|
idrequired | string | |
urlrequired | string | |
descriptionrequired | string | Can be null. |
statusrequired | string | One of |
eventTypesrequired | string[] | |
domainIdsrequired | integer[] | |
apiVersionrequired | string | Can be null. |
consecutiveFailuresrequired | integer | |
lastFailureAtrequired | string | Can be null. |
lastSuccessAtrequired | string | Can be null. |
secretHintrequired | string | Always masked. The signing secret is returned once, when the webhook is created or its secret is rotated. |
createdAtrequired | string | |
updatedAtrequired | string |
400The URL is not a public https URL, or the body is not valid.
403`SCOPE_DENIED`: the key is not a full access key (send-only, read-only, sandbox-only or live AI). `DOMAIN_PINNED`: the key is limited to one domain. `FORBIDDEN`: the key was made by a team member without admin rights, or before Notix recorded who made each key (create a new key).
404No webhook with this id for the calling team, or one of `domainIds` is not the team's.
Pause or resume a webhook
POST/v1/webhooks/{webhookId}/statusFull access key
Set status to PAUSED to stop sending events, or ACTIVE to start again. Resuming also clears the failure count, so a webhook Notix turned off after repeated failures (AUTO_DISABLED) can be turned back on once your endpoint works.
Who can call it
A full access key created by a team admin, as on the dashboard. Every refusal is 403:
| Code | Why |
|---|---|
SCOPE_DENIED | The key is not a full access key (send-only, read-only, sandbox-only or live AI). |
DOMAIN_PINNED | The key is limited to one domain. A webhook receives the events of every domain. |
FORBIDDEN | The key was made by a team member without admin rights, or before Notix recorded who made each key. Create a new key as a team admin. |
Adding a webhook, pointing one at a new URL or removing one emails the team's owner and admins, naming the key.
| Field | Type | About |
|---|---|---|
webhookIdrequired | string |
| Field | Type | About |
|---|---|---|
statusrequired | string | ACTIVE sends events again (and clears the failure count); PAUSED stops sending them. One of |
curl -X POST "https://app.usenotix.dev/api/v1/webhooks/<webhookId>/status" \
-H "Authorization: Bearer $NOTIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "PAUSED"
}'
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.setStatus("wh_123", "PAUSED");
Responses
200The webhook with its new status.
| Field | Type | About |
|---|---|---|
idrequired | string | |
urlrequired | string | |
descriptionrequired | string | Can be null. |
statusrequired | string | One of |
eventTypesrequired | string[] | |
domainIdsrequired | integer[] | |
apiVersionrequired | string | Can be null. |
consecutiveFailuresrequired | integer | |
lastFailureAtrequired | string | Can be null. |
lastSuccessAtrequired | string | Can be null. |
secretHintrequired | string | Always masked. The signing secret is returned once, when the webhook is created or its secret is rotated. |
createdAtrequired | string | |
updatedAtrequired | string |
400`status` is not ACTIVE or PAUSED.
403`SCOPE_DENIED`: the key is not a full access key (send-only, read-only, sandbox-only or live AI). `DOMAIN_PINNED`: the key is limited to one domain. `FORBIDDEN`: the key was made by a team member without admin rights, or before Notix recorded who made each key (create a new key).
404No webhook with this id for the calling team.
Delete a webhook
DELETE/v1/webhooks/{webhookId}Full access key
Stop sending events to this URL for good. The webhook and its call log are deleted, and this cannot be undone. To stop events for a while, pause it instead.
Who can call it
A full access key created by a team admin, as on the dashboard. Every refusal is 403:
| Code | Why |
|---|---|
SCOPE_DENIED | The key is not a full access key (send-only, read-only, sandbox-only or live AI). |
DOMAIN_PINNED | The key is limited to one domain. A webhook receives the events of every domain. |
FORBIDDEN | The key was made by a team member without admin rights, or before Notix recorded who made each key. Create a new key as a team admin. |
Adding a webhook, pointing one at a new URL or removing one emails the team's owner and admins, naming the key.
| Field | Type | About |
|---|---|---|
webhookIdrequired | string |
curl -X DELETE "https://app.usenotix.dev/api/v1/webhooks/<webhookId>" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.delete("wh_123");
Responses
200The webhook and its call log were deleted.
| Field | Type | About |
|---|---|---|
idrequired | string | |
deletedrequired | boolean | One of |
403`SCOPE_DENIED`: the key is not a full access key (send-only, read-only, sandbox-only or live AI). `DOMAIN_PINNED`: the key is limited to one domain. `FORBIDDEN`: the key was made by a team member without admin rights, or before Notix recorded who made each key (create a new key).
404No webhook with this id for the calling team.
Send a test event
POST/v1/webhooks/{webhookId}/testFull access key
Queue a webhook.test event to the webhook's saved URL, signed like a real event. The answer is 202 with the callId; read the call to see how your endpoint answered. The request takes no URL: a test only ever goes to the URL saved on the webhook. A team can send 10 test events a minute.
Who can call it
A full access key created by a team admin, as on the dashboard. Every refusal is 403:
| Code | Why |
|---|---|
SCOPE_DENIED | The key is not a full access key (send-only, read-only, sandbox-only or live AI). |
DOMAIN_PINNED | The key is limited to one domain. A webhook receives the events of every domain. |
FORBIDDEN | The key was made by a team member without admin rights, or before Notix recorded who made each key. Create a new key as a team admin. |
Adding a webhook, pointing one at a new URL or removing one emails the team's owner and admins, naming the key.
| Field | Type | About |
|---|---|---|
webhookIdrequired | string |
curl -X POST "https://app.usenotix.dev/api/v1/webhooks/<webhookId>/test" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.test("wh_123");
console.log(data?.callId);
Responses
202A webhook.test event was queued for the webhook's saved URL.
| Field | Type | About |
|---|---|---|
callIdrequired | string |
403`SCOPE_DENIED`: the key is not a full access key (send-only, read-only, sandbox-only or live AI). `DOMAIN_PINNED`: the key is limited to one domain. `FORBIDDEN`: the key was made by a team member without admin rights, or before Notix recorded who made each key (create a new key).
404No webhook with this id for the calling team.
429The team sent 10 test events in the last minute.
Rotate the signing secret
POST/v1/webhooks/{webhookId}/rotate-secretFull access key
Replace the signing secret with a new one. The answer is the only time Notix shows the new secret. The old secret stops working at once, so update your endpoint straight away. A team can rotate 10 secrets a minute.
Who can call it
A full access key created by a team admin, as on the dashboard. Every refusal is 403:
| Code | Why |
|---|---|
SCOPE_DENIED | The key is not a full access key (send-only, read-only, sandbox-only or live AI). |
DOMAIN_PINNED | The key is limited to one domain. A webhook receives the events of every domain. |
FORBIDDEN | The key was made by a team member without admin rights, or before Notix recorded who made each key. Create a new key as a team admin. |
Adding a webhook, pointing one at a new URL or removing one emails the team's owner and admins, naming the key.
| Field | Type | About |
|---|---|---|
webhookIdrequired | string |
curl -X POST "https://app.usenotix.dev/api/v1/webhooks/<webhookId>/rotate-secret" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.rotateSecret("wh_123");
// The old secret stops working now: update your app with data.secret.
Responses
200The new signing secret, shown only here. The old one stops working at once.
| Field | Type | About |
|---|---|---|
idrequired | string | |
urlrequired | string | |
descriptionrequired | string | Can be null. |
statusrequired | string | One of |
eventTypesrequired | string[] | |
domainIdsrequired | integer[] | |
apiVersionrequired | string | Can be null. |
consecutiveFailuresrequired | integer | |
lastFailureAtrequired | string | Can be null. |
lastSuccessAtrequired | string | Can be null. |
secretHintrequired | string | Always masked. The signing secret is returned once, when the webhook is created or its secret is rotated. |
createdAtrequired | string | |
updatedAtrequired | string | |
secretrequired | string | The signing secret. Shown only in this response: store it now. Notix never shows it again. |
403`SCOPE_DENIED`: the key is not a full access key (send-only, read-only, sandbox-only or live AI). `DOMAIN_PINNED`: the key is limited to one domain. `FORBIDDEN`: the key was made by a team member without admin rights, or before Notix recorded who made each key (create a new key).
404No webhook with this id for the calling team.
429The team rotated 10 secrets in the last minute.
List webhook calls
GET/v1/webhooks/callsFull access key
The call log: every event Notix sent, or tried to send, to your webhooks, newest first. Filter by webhookId and status (PENDING, IN_PROGRESS, DELIVERED, FAILED, DISCARDED). Up to 50 per page (20 by default); pass nextCursor back as cursor for the next page. Calls are kept for 30 days.
| Field | Type | About |
|---|---|---|
webhookId | string | Only calls to this webhook. |
status | string | One of |
limit | integer | Calls per page, 1 to 50. Defaults to 20. Default |
cursor | string | The `nextCursor` of the previous page. |
curl -X GET "https://app.usenotix.dev/api/v1/webhooks/calls" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.listCalls({ webhookId: "wh_123", status: "FAILED", limit: 20 });
const next = data?.nextCursor; // pass back as cursor for the next page
Responses
200A page of webhook calls, newest first, with the cursor for the next page.
| Field | Type | About |
|---|---|---|
datarequired | object[] | |
data[].idrequired | string | |
data[].webhookIdrequired | string | |
data[].typerequired | string | The event type, such as email.delivered or webhook.test. |
data[].statusrequired | string | One of |
data[].attemptrequired | integer | Delivery attempts made so far. |
data[].nextAttemptAtrequired | string | Can be null. |
data[].lastErrorrequired | string | Can be null. |
data[].responseStatusrequired | integer | Can be null. |
data[].responseTimeMsrequired | integer | Can be null. |
data[].createdAtrequired | string | |
data[].updatedAtrequired | string | |
nextCursorrequired | string | Can be null. |
400The cursor is not a call of this team, or a filter is not valid.
403`SCOPE_DENIED`: a send-only key or a live AI key. `DOMAIN_PINNED`: the key is limited to one domain, and webhooks carry every domain's events.
Get a webhook call
GET/v1/webhooks/calls/{callId}Full access key
One call: the event data Notix sent (payload, as JSON text), each attempt's outcome, the HTTP status your endpoint answered, how long it took and the first 2 KB of its response body. Response headers are never stored, and the signing secret is never part of a call.
| Field | Type | About |
|---|---|---|
callIdrequired | string |
curl -X GET "https://app.usenotix.dev/api/v1/webhooks/calls/<callId>" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.getCall("whc_123");
console.log(data?.responseStatus, data?.responseText);
Responses
200The call, with the event data sent and the start of the response.
| Field | Type | About |
|---|---|---|
idrequired | string | |
webhookIdrequired | string | |
typerequired | string | The event type, such as email.delivered or webhook.test. |
statusrequired | string | One of |
attemptrequired | integer | Delivery attempts made so far. |
nextAttemptAtrequired | string | Can be null. |
lastErrorrequired | string | Can be null. |
responseStatusrequired | integer | Can be null. |
responseTimeMsrequired | integer | Can be null. |
createdAtrequired | string | |
updatedAtrequired | string | |
apiVersionrequired | string | Can be null. |
payloadrequired | string | The event data Notix sent, as JSON text. |
responseTextrequired | string | The first 2 KB of your endpoint's response body. Response headers are never stored. Can be null. |
403`SCOPE_DENIED`: a send-only key or a live AI key. `DOMAIN_PINNED`: the key is limited to one domain, and webhooks carry every domain's events.
404No webhook call with this id for the calling team.
Retry a webhook call
POST/v1/webhooks/calls/{callId}/retryFull access key
Send a failed or dropped call again, with the same event data, to its webhook's saved URL. The call goes back to PENDING and starts a fresh set of attempts. The answer is 202. A call that was delivered, or is still being delivered, answers 409 CONFLICT. A team can retry 30 calls a minute. Any full access key can retry, as any team member can on the dashboard; other keys answer 403 SCOPE_DENIED, and a key limited to one domain 403 DOMAIN_PINNED.
| Field | Type | About |
|---|---|---|
callIdrequired | string |
curl -X POST "https://app.usenotix.dev/api/v1/webhooks/calls/<callId>/retry" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.webhookEndpoints.retryCall("whc_123");
Responses
202The call was queued again, to its webhook's saved URL.
| Field | Type | About |
|---|---|---|
callIdrequired | string |