Skip to content
This repository was archived by the owner on Jun 26, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions content/docs/platform/developer/webhooks/event-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,34 @@

## Message events

### Where message events come from

Not every `message.*` event is triggered the same way. Some fire when Novu sends a notification; others arrive later from your delivery provider or from subscriber actions in the Inbox.

Check notice on line 20 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L20

[Google.Passive] In general, use active voice instead of passive voice ('is triggered').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is triggered').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 20, "column": 29}}}, "severity": "INFO"}

Check warning on line 20 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L20

[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 20, "column": 29}}}, "severity": "WARNING"}

Check notice on line 20 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L20

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 20, "column": 29}}}, "severity": "INFO"}

Check notice on line 20 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L20

[Google.Semicolons] Use semicolons judiciously.
Raw output
{"message": "[Google.Semicolons] Use semicolons judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 20, "column": 96}}}, "severity": "INFO"}

| Event | Typical source | Requires [Email Activity Tracking](/platform/integrations/email/activity-tracking)? |
| ----- | -------------- | ----------------------------------------------------------------------------------- |
| `message.sent` | Novu worker, right after the provider accepts the send | No |
| `message.failed` | Novu worker (send error) or provider bounce/drop via activity tracking | Partial — provider failures need activity tracking |

Check notice on line 25 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L25

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 25, "column": 34}}}, "severity": "INFO"}

Check failure on line 25 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L25

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 25, "column": 102}}}, "severity": "ERROR"}
| `message.delivered` | Provider inbound webhook (for example, SendGrid `delivered`) | Yes, for email |

Check notice on line 26 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L26

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 26, "column": 1}}}, "severity": "INFO"}
| `message.seen` | Provider inbound webhook (for example, SendGrid `open`) or Inbox | Yes, for email opens from the provider |

Check notice on line 27 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L27

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 27, "column": 1}}}, "severity": "INFO"}

<Callout>
**`message.sent` vs `message.delivered` (email)**

- **`message.sent`** means Novu successfully handed the email to SendGrid (or another provider). It fires immediately.

Check warning on line 32 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L32

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 32, "column": 1}}}, "severity": "WARNING"}
- **`message.delivered`** means the provider later confirmed the recipient's mail server received the email. It only arrives if [Email Activity Tracking](/platform/integrations/email/activity-tracking) is enabled on the integration that sent the message.

Check warning on line 33 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L33

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 33, "column": 1}}}, "severity": "WARNING"}

Check warning on line 33 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L33

[write-good.Passive] 'is enabled' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is enabled' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 33, "column": 203}}}, "severity": "WARNING"}

Check notice on line 33 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L33

[Google.Passive] In general, use active voice instead of passive voice ('is enabled').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is enabled').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 33, "column": 203}}}, "severity": "INFO"}

Check notice on line 33 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L33

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 33, "column": 203}}}, "severity": "INFO"}

Seeing `message.sent` in your outbound webhook logs does not mean delivery or open tracking is configured.

Check notice on line 35 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L35

[Google.Contractions] Use 'doesn't' instead of 'does not'.
Raw output
{"message": "[Google.Contractions] Use 'doesn't' instead of 'does not'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 35, "column": 53}}}, "severity": "INFO"}

Check notice on line 35 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L35

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 35, "column": 93}}}, "severity": "INFO"}

Check warning on line 35 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L35

[write-good.Passive] 'is configured' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is configured' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 35, "column": 93}}}, "severity": "WARNING"}

Check notice on line 35 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L35

[Google.Passive] In general, use active voice instead of passive voice ('is configured').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is configured').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 35, "column": 93}}}, "severity": "INFO"}
</Callout>

### Event reference

- `message.archived`: This webhook is triggered when a subscriber archives a message. The payload contains the details of the event.
- `message.delivered`: This webhook is triggered when a message delivery provider acknowledged the message delivery to the end receiving client. The payload contains the details of the event.
- `message.failed`: This webhook is triggered when Novu tries to send the message to the delivery provider and it got failed. The payload contains the details of the event.
- `message.read`: This webhook is triggered when a message has been read by the subscriber. The payload contains the details of the event.
- `message.seen`: This webhook is triggered when a subscriber opens a message. The payload contains the details of the event.
- `message.sent`: This webhook is triggered when Novu sends the message to the delivery provider. The payload contains the details of the event.
- `message.delivered`: This webhook is triggered when a delivery provider confirms the message was delivered to the recipient's mail server (for email, via [activity tracking](/platform/integrations/email/activity-tracking)). The payload contains the details of the event.

Check notice on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 1}}}, "severity": "INFO"}

Check warning on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 1}}}, "severity": "WARNING"}

Check notice on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[Google.Passive] In general, use active voice instead of passive voice ('is triggered').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is triggered').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 37}}}, "severity": "INFO"}

Check notice on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 37}}}, "severity": "INFO"}

Check warning on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 37}}}, "severity": "WARNING"}

Check notice on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[write-good.E-Prime] Try to avoid using 'was'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'was'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 96}}}, "severity": "INFO"}

Check notice on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[Google.Passive] In general, use active voice instead of passive voice ('was delivered').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('was delivered').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 96}}}, "severity": "INFO"}

Check warning on line 41 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L41

[write-good.Passive] 'was delivered' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'was delivered' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 41, "column": 96}}}, "severity": "WARNING"}
- `message.failed`: This webhook is triggered when Novu fails to send the message to the provider, or when the provider reports a bounce, drop, or block (via activity tracking). The payload contains the details of the event.

Check warning on line 42 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L42

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 42, "column": 1}}}, "severity": "WARNING"}

Check notice on line 42 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L42

[Google.Passive] In general, use active voice instead of passive voice ('is triggered').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is triggered').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 42, "column": 34}}}, "severity": "INFO"}

Check notice on line 42 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L42

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 42, "column": 34}}}, "severity": "INFO"}

Check warning on line 42 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L42

[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 42, "column": 34}}}, "severity": "WARNING"}

Check notice on line 42 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L42

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 42, "column": 154}}}, "severity": "INFO"}
- `message.read`: This webhook is triggered when a message has been read by the subscriber (for example, via the Inbox). The payload contains the details of the event.

Check warning on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 1}}}, "severity": "WARNING"}

Check notice on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 32}}}, "severity": "INFO"}

Check warning on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 32}}}, "severity": "WARNING"}

Check notice on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[Google.Passive] In general, use active voice instead of passive voice ('is triggered').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is triggered').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 32}}}, "severity": "INFO"}

Check notice on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[Google.Passive] In general, use active voice instead of passive voice ('been read').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('been read').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 64}}}, "severity": "INFO"}

Check warning on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[write-good.Passive] 'been read' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'been read' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 64}}}, "severity": "WARNING"}

Check notice on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[write-good.E-Prime] Try to avoid using 'been'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'been'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 64}}}, "severity": "INFO"}

Check notice on line 43 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L43

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 43, "column": 92}}}, "severity": "INFO"}
- `message.seen`: This webhook is triggered when a message is opened. For email, provider open events (for example, SendGrid `open`) are forwarded as `message.seen` — not `message.opened`. The payload contains the details of the event.

Check warning on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 1}}}, "severity": "WARNING"}

Check notice on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 1}}}, "severity": "INFO"}

Check warning on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 32}}}, "severity": "WARNING"}

Check notice on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 32}}}, "severity": "INFO"}

Check notice on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[Google.Passive] In general, use active voice instead of passive voice ('is triggered').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is triggered').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 32}}}, "severity": "INFO"}

Check warning on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[write-good.Passive] 'is opened' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is opened' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 60}}}, "severity": "WARNING"}

Check notice on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[Google.Passive] In general, use active voice instead of passive voice ('is opened').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is opened').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 60}}}, "severity": "INFO"}

Check notice on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 134}}}, "severity": "INFO"}

Check warning on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[write-good.Passive] 'are forwarded' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are forwarded' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 134}}}, "severity": "WARNING"}

Check notice on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[Google.Passive] In general, use active voice instead of passive voice ('are forwarded').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are forwarded').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 134}}}, "severity": "INFO"}

Check failure on line 44 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L44

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 44, "column": 165}}}, "severity": "ERROR"}
- `message.sent`: This webhook is triggered when Novu hands the message off to the delivery provider. The payload contains the details of the event.

Check warning on line 45 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L45

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 45, "column": 1}}}, "severity": "WARNING"}

Check notice on line 45 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L45

[Google.Passive] In general, use active voice instead of passive voice ('is triggered').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is triggered').", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 45, "column": 32}}}, "severity": "INFO"}

Check notice on line 45 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L45

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 45, "column": 32}}}, "severity": "INFO"}

Check warning on line 45 in content/docs/platform/developer/webhooks/event-types.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/event-types.mdx#L45

[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is triggered' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/event-types.mdx", "range": {"start": {"line": 45, "column": 32}}}, "severity": "WARNING"}
- `message.snoozed`: This webhook is triggered when a message is snoozed by the subscriber. The payload contains the details of the event.
- `message.unarchived`: This webhook is triggered when an archived message is unarchived. The payload contains the details of the event.
- `message.unread`: This webhook is triggered when a message is unread or marked as unread by the subscriber. The payload contains the details of the event.
Expand Down
19 changes: 19 additions & 0 deletions content/docs/platform/developer/webhooks/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,22 @@
### How can I test webhooks locally?

To test webhooks locally, you can use a service like ngrok or localtunnel to expose your local server to the internet. Alternatively, you can use webhook testing services like Webhook.site or RequestBin to inspect webhook payloads.

### Why do I see `message.sent` but not `message.delivered` or `message.seen` for email?

Check warning on line 194 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L194

[Google.FirstPerson] Avoid first-person pronouns such as ' I '.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as ' I '.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 194, "column": 11}}}, "severity": "WARNING"}

These events come from different stages of the email lifecycle:

- **`message.sent`** — Novu successfully sent the email to the provider (for example, SendGrid). This does not require [Email Activity Tracking](/platform/integrations/email/activity-tracking).

Check warning on line 198 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L198

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 198, "column": 1}}}, "severity": "WARNING"}

Check failure on line 198 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L198

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 198, "column": 21}}}, "severity": "ERROR"}

Check notice on line 198 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L198

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 198, "column": 73}}}, "severity": "INFO"}

Check notice on line 198 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L198

[Google.Contractions] Use 'doesn't' instead of 'does not'.
Raw output
{"message": "[Google.Contractions] Use 'doesn't' instead of 'does not'.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 198, "column": 103}}}, "severity": "INFO"}
- **`message.delivered`** — The provider confirmed delivery to the recipient's mail server. Requires activity tracking on the sending integration.

Check warning on line 199 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L199

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 199, "column": 1}}}, "severity": "WARNING"}

Check failure on line 199 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L199

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 199, "column": 26}}}, "severity": "ERROR"}
- **`message.seen`** — The recipient opened the email. SendGrid `open` events are forwarded as `message.seen` (not `message.opened`). Requires activity tracking on the sending integration.

Check notice on line 200 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L200

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 200, "column": 1}}}, "severity": "INFO"}

Check warning on line 200 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L200

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 200, "column": 1}}}, "severity": "WARNING"}

Check failure on line 200 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L200

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 200, "column": 21}}}, "severity": "ERROR"}

Check notice on line 200 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L200

[Google.Passive] In general, use active voice instead of passive voice ('are forwarded').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are forwarded').", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 200, "column": 79}}}, "severity": "INFO"}

Check notice on line 200 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L200

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 200, "column": 79}}}, "severity": "INFO"}

Check warning on line 200 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L200

[write-good.Passive] 'are forwarded' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are forwarded' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 200, "column": 79}}}, "severity": "WARNING"}

You need both [Email Activity Tracking](/platform/integrations/email/activity-tracking) on your provider integration **and** an outbound webhook endpoint subscribed to `message.delivered` and `message.seen`.

### I see delivery or open events in SendGrid but not in Novu outbound webhooks

Check warning on line 204 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L204

[Google.FirstPerson] Avoid first-person pronouns such as 'I '.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as 'I '.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 204, "column": 5}}}, "severity": "WARNING"}

1. Confirm **Email Activity Tracking** is enabled on the SendGrid integration that sends your emails.

Check warning on line 206 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L206

[write-good.Passive] 'is enabled' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is enabled' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 206, "column": 40}}}, "severity": "WARNING"}

Check notice on line 206 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L206

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 206, "column": 40}}}, "severity": "INFO"}

Check notice on line 206 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L206

[Google.Passive] In general, use active voice instead of passive voice ('is enabled').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is enabled').", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 206, "column": 40}}}, "severity": "INFO"}
2. Confirm your outbound endpoint is subscribed to **`message.seen`** and **`message.delivered`** (not `message.opened`).

Check notice on line 207 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L207

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 207, "column": 1}}}, "severity": "INFO"}

Check notice on line 207 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L207

[Google.Passive] In general, use active voice instead of passive voice ('is subscribed').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is subscribed').", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 207, "column": 35}}}, "severity": "INFO"}

Check warning on line 207 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L207

[write-good.Passive] 'is subscribed' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is subscribed' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 207, "column": 35}}}, "severity": "WARNING"}

Check notice on line 207 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L207

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 207, "column": 35}}}, "severity": "INFO"}
3. In SendGrid → **Event Webhook** → check **delivery history** for POSTs to your Novu inbound URL. The SendGrid activity UI and third-party integrations can show events that were never posted to Novu.

Check notice on line 208 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L208

[write-good.E-Prime] Try to avoid using 'were'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'were'.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 208, "column": 176}}}, "severity": "INFO"}
4. If you have multiple SendGrid integrations, verify the Event Webhook **Post URL** matches the [Inbound Webhook URL](/platform/integrations/email/activity-tracking/manual-configuration/sendgrid) for the integration that sends the emails.

Check warning on line 209 in content/docs/platform/developer/webhooks/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/index.mdx#L209

[write-good.TooWordy] 'multiple' is too wordy.
Raw output
{"message": "[write-good.TooWordy] 'multiple' is too wordy.", "location": {"path": "content/docs/platform/developer/webhooks/index.mdx", "range": {"start": {"line": 209, "column": 16}}}, "severity": "WARNING"}

See the [SendGrid activity tracking troubleshooting](/platform/integrations/email/activity-tracking/manual-configuration/sendgrid#troubleshooting) section for more detail.
205 changes: 5 additions & 200 deletions content/docs/platform/developer/webhooks/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,204 +4,9 @@
description: "Learn how Novu webhooks work, including event types, payload schemas, signature verification, and endpoint configuration."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The description frontmatter still describes the old full-featured page. Search engines and Mintlify previews will surface this stale text when users land on what is now a near-empty legacy-notice page.

Suggested change
description: "Learn how Novu webhooks work, including event types, payload schemas, signature verification, and endpoint configuration."
description: "This page has moved. See the Webhooks overview for setup, signatures, and retries, and the Event types page for a full list of supported events."
Prompt To Fix With AI
This is a comment left during a code review.
Path: content/docs/platform/developer/webhooks/webhooks.mdx
Line: 4

Comment:
The `description` frontmatter still describes the old full-featured page. Search engines and Mintlify previews will surface this stale text when users land on what is now a near-empty legacy-notice page.

```suggestion
description: "This page has moved. See the Webhooks overview for setup, signatures, and retries, and the Event types page for a full list of supported events."
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor

---

## The Intro
<Callout>
This page is a legacy draft. Use the current documentation instead:

Check notice on line 8 in content/docs/platform/developer/webhooks/webhooks.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/webhooks.mdx#L8

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/developer/webhooks/webhooks.mdx", "range": {"start": {"line": 8, "column": 11}}}, "severity": "INFO"}

Webhooks are how services notify each other of events.

At their core they are just a POST request to a pre-determined endpoint.
The endpoint can be whatever you want, and you can just add them from the UI.
You normally use one endpoint per service, and that endpoint listens to all of the event types.

For example, if you receive webhooks from Novu, you can structure your URL like: `https://www.example.com/novu/webhook/`.

The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (15s).

It's also important to disable CSRF protection for this endpoint if the framework you use enables them by default.

Another important aspect of handling webhooks is to verify the signature and timestamp when processing them.

You can learn more about it in the signature verification section.

## Events and event types

The core value of webhooks is to notify users when events happen, so it's extremely important to understand what events are available and their payload schemas.

Novu webhooks allow you to receive notifications when specific events occur in your Novu account. These events include workflow updates, subscriber changes, and message delivery status updates.

### Supported event types

Novu supports the following webhook event types:

- **Workflow Events**: Events about workflow creation, updates, and deletions,
- **Message Events**: Events about message delivery status changes
- **Preference Events**: Events about subscriber preference changes

Each event includes detailed information about the affected resource and the changes that occurred.

#### Workflow Events

- **workflow.created**: Triggered when a workflow is created
- **workflow.updated**: Triggered when a workflow is updated
- **workflow.deleted**: Triggered when a workflow is deleted
- **workflow.published**: Triggered when a workflow is synced from dev to prod environment

#### Message Events

- **messages.archived**: Triggered when a message is archived.
- **messages.unarchived**: Triggered when a message is unarchived.
- **messages.read**: Triggered when a message is read.
- **messages.unread**: Triggered when a message is unread.
- **messages.seen**: Triggered when a message is seen.
- **messages.snoozed**: Triggered when a message is snoozed.
- **messages.unsnoozed**: Triggered when a message is unsnoozed.
- **messages.sent**: Triggered when Novu sends the message to the delivery provider.
- **messages.delivered**: Triggered when a message delivery provider acknowledged the message delivery to the end receiving client.
- **messages.failed**: Triggered when Novu tried to send the message to the delivery provider and it got failed.

#### Preference Events

- **preference.updated**: Triggered when subscriber preference is updated.

## How to add an endpoint

To start listening to messages, you will need to configure your endpoints.

1. Go to the [Webhooks](https://dashboard.novu.co/webhooks) page in the Novu dashboard.
2. Click **Add Endpoint**.
3. Enter the URL of your endpoint.
4. Add description for this webhook endpoint.
5. Select the event types you want to listen to.
6. Optional: add advanced configuration for your endpoint.
7. Click **Create**.

If your endpoint isn't quite ready to start receiving events, you can use a service like [Webhook.site](https://webhook.site/) or [RequestBin](https://requestbin.com/) to have a unique URL generated for you.

## How to test endpoints

Once you've added an endpoint, you'll want to make sure it's working.

The "Testing" tab lets you send test events to your endpoint.

After sending an example event, you can view the message payload, all of the message attempts, and whether it succeeded or failed.

## Verifying signatures

Webhook signatures let you verify that webhook messages are actually sent by Novu and not a malicious actor.

Verifying webhook signatures helps ensure payloads were sent by Novu and have not been tampered with. Here is an example using JavaScript:

```javascript
import { Webhook } from "svix";

const secret = "YOUR_WEBHOOK_SECRET_KEY";

// These were all sent from the server
const headers = {
"webhook-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"webhook-timestamp": "1614265330",
"webhook-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';

const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const verifiedPayload = wh.verify(payload, headers);
```

See the library documentation for more instructions and examples of how to verify signatures in other languages.

## Retry schedule

### Retries

We attempt to deliver each webhook message based on a retry schedule with exponential backoff.

#### The schedule

Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:

- Immediately
- 5 seconds
- 5 minutes
- 30 minutes
- 2 hours
- 5 hours
- 10 hours
- 10 hours (in addition to the previous)

If an endpoint is removed or disabled, delivery attempts to the endpoint will be disabled as well.

For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.

### Manual retries

You can also use the application portal to manually retry each message at any time, or automatically retry ("Recover") all failed messages starting from a given date.

## Troubleshooting & Failure Recovery

### Common reasons why your webhook endpoint is failing

There are some common reasons why your webhook endpoint is failing:

- **Not using the raw payload body**

This is the most common issue. When generating the signed content, we use the raw string body of the message payload.

If you convert JSON payloads into strings using methods like stringify, different implementations may produce different string representations of the JSON object, which can lead to discrepancies when verifying the signature. It's crucial to verify the payload exactly as it was sent, byte-for-byte or string-for-string, to ensure accurate verification.

- **Missing the secret key**

From time to time we see people simply using the wrong secret key. Remember that keys are unique to endpoints.

- **Sending the wrong response codes**

When we receive a response with a 2xx status code, we interpret that as a successful delivery even if you indicate a failure in the response payload. Make sure to use the right response status codes so we know when messages are supposed to succeed vs fail.

- **Responses timing out**

We will consider any message that fails to send a response within 15 seconds a failed message. If your endpoint is also processing complicated workflows, it may timeout and result in failed messages.

We suggest having your endpoint simply receive the message and add it to a queue to be processed asynchronously so you can respond promptly and avoid getting timed out.

### Re-enable a disabled endpoint

If all attempts to a specific endpoint fail for a period of 5 days, the endpoint will be disabled. To re-enable a disabled endpoint, go to the webhook dashboard, find the endpoint from the list and select "Enable Endpoint".

### Recovering/Resending failed messages

If your service has downtime or if your endpoint was misconfigured, you probably want to recover any messages that failed during the downtime.

If you want to replay a single event, you can find the message from the UI and click the options menu next to any of the attempts.

From there, click "resend" to have the same message send to your endpoint again.

If you need to recover from a service outage and want to replay all the events since a given time, you can do so from the Endpoint page. On an endpoint's details page, click "Options > Recover Failed Messages".

From there, you can choose a time window to recover from.

For a more granular recovery - for example, if you know the exact timestamp that you want to recover from - you can click the options menu on any message from the endpoint page.

From there, you can click "Replay..." and choose to "Replay all failed messages since this time."

## FAQs

### How do I secure my webhook endpoint?

To secure your webhook endpoint, you should:

1. Verify the webhook signature using the official verification library
2. Use HTTPS for your endpoint URL
3. Implement rate limiting to prevent abuse
4. Keep your webhook secret secure and rotate it periodically

### What happens if my endpoint is unavailable?

If your endpoint is unavailable, Novu will retry sending the webhook according to the retry schedule. If all attempts fail for 5 days, the endpoint will be disabled and you'll need to manually re-enable it.

### Can I filter webhooks by event type?

Yes, when configuring your webhook endpoint, you can select specific event types to receive. This allows you to filter out events that aren't relevant to your use case.

### How can I test webhooks locally?

To test webhooks locally, you can use a service like ngrok or localtunnel to expose your local server to the internet. Alternatively, you can use webhook testing services like Webhook.site or RequestBin to inspect webhook payloads.
- [Webhooks overview](/platform/developer/webhooks) — setup, signatures, retries, and troubleshooting

Check warning on line 10 in content/docs/platform/developer/webhooks/webhooks.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/webhooks.mdx#L10

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/webhooks.mdx", "range": {"start": {"line": 10, "column": 1}}}, "severity": "WARNING"}

Check failure on line 10 in content/docs/platform/developer/webhooks/webhooks.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/webhooks.mdx#L10

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/developer/webhooks/webhooks.mdx", "range": {"start": {"line": 10, "column": 52}}}, "severity": "ERROR"}
- [Event types](/platform/developer/webhooks/event-types) — full list of `message.*`, `workflow.*`, and `preference.*` events

Check warning on line 11 in content/docs/platform/developer/webhooks/webhooks.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/webhooks.mdx#L11

[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).
Raw output
{"message": "[Novu.BulletStyle] Prefer '*' over '-' for bullet points (ignore inside code fences).", "location": {"path": "content/docs/platform/developer/webhooks/webhooks.mdx", "range": {"start": {"line": 11, "column": 1}}}, "severity": "WARNING"}

Check failure on line 11 in content/docs/platform/developer/webhooks/webhooks.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/developer/webhooks/webhooks.mdx#L11

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/developer/webhooks/webhooks.mdx", "range": {"start": {"line": 11, "column": 58}}}, "severity": "ERROR"}
</Callout>
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
When enabled, Novu will auto-configure delivery webhooks using your existing API key.
![Inbound webhook enabled](/images/developer-tools/inbound-enabled.png)

<Callout>
**Activity tracking and outbound webhooks are two separate steps**

Check notice on line 31 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L31

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 31, "column": 43}}}, "severity": "INFO"}

1. **Email Activity Tracking** (this guide) — lets Novu receive delivery and engagement events from your provider via an inbound webhook.

Check notice on line 33 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L33

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 33, "column": 32}}}, "severity": "INFO"}

Check failure on line 33 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L33

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 33, "column": 44}}}, "severity": "ERROR"}
2. **[Outbound Webhooks](/platform/developer/webhooks)** — forwards selected events to your own endpoint. Subscribe to `message.delivered` and `message.seen` to receive provider delivery and open events.

Check failure on line 34 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L34

[Google.EmDash] Don't put a space before or after a dash.
Raw output
{"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 34, "column": 57}}}, "severity": "ERROR"}

Enabling only outbound webhooks is not enough. You must also enable activity tracking on the provider integration that sends your emails.

Check notice on line 36 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L36

[write-good.E-Prime] Try to avoid using 'is'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'is'.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 36, "column": 33}}}, "severity": "INFO"}

Check notice on line 36 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L36

[Google.Contractions] Use 'isn't' instead of 'is not'.
Raw output
{"message": "[Google.Contractions] Use 'isn't' instead of 'is not'.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 36, "column": 33}}}, "severity": "INFO"}
</Callout>

## Manual configuration

If auto-configuration fails or isn’t supported for your provider, you will need to set it up manually. You can do that by following these provider specific guides:
Expand Down Expand Up @@ -133,9 +142,23 @@
How it works:

1. A provider emits an event (for example, delivered).
2. Novu receives and normalizes the event (for example, → delivered).
2. Novu receives and normalizes the event (for example, → `delivered`).

Check notice on line 145 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L145

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 145, "column": 1}}}, "severity": "INFO"}
3. Novu updates its internal message entity and step-run trace.
4. If outbound webhooks are enabled, Novu forwards the normalized event to your configured outbound webhook endpoint.
4. If [outbound webhooks](/platform/developer/webhooks) are enabled, Novu forwards the event to your configured endpoint using the outbound event names below.

Check notice on line 147 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L147

[Google.Passive] In general, use active voice instead of passive voice ('are enabled').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('are enabled').", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 147, "column": 57}}}, "severity": "INFO"}

Check warning on line 147 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L147

[write-good.Passive] 'are enabled' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are enabled' may be passive voice. Use active voice if you can.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 147, "column": 57}}}, "severity": "WARNING"}

Check notice on line 147 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L147

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 147, "column": 57}}}, "severity": "INFO"}

#### Provider events → outbound webhook events

| Provider event (example) | Novu activity status | Outbound webhook event |

Check notice on line 151 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L151

[Google.Parens] Use parentheses judiciously.
Raw output
{"message": "[Google.Parens] Use parentheses judiciously.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 151, "column": 18}}}, "severity": "INFO"}
| ---------------------- | -------------------- | ---------------------- |
| `delivered` | `delivered` | `message.delivered` |
| `open` | `opened` | `message.seen` |
| `bounce`, `dropped`, `blocked` | `bounced`, `dropped`, `blocked` | `message.failed` |

<Callout>
Email opens appear as **`message.seen`** in outbound webhooks, not `message.opened`. Subscribe to `message.seen` on your outbound webhook endpoint.

Check warning on line 158 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L158

[Novu.SentenceLength] Sentences should not exceed 25 words; this one has 27.
Raw output
{"message": "[Novu.SentenceLength] Sentences should not exceed 25 words; this one has 27.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 158, "column": 86}}}, "severity": "WARNING"}

Click events appear in the Novu activity feed but are **not** currently forwarded to outbound webhooks for email.

Check notice on line 160 in content/docs/platform/integrations/email/activity-tracking/index.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] content/docs/platform/integrations/email/activity-tracking/index.mdx#L160

[write-good.E-Prime] Try to avoid using 'are'.
Raw output
{"message": "[write-good.E-Prime] Try to avoid using 'are'.", "location": {"path": "content/docs/platform/integrations/email/activity-tracking/index.mdx", "range": {"start": {"line": 160, "column": 51}}}, "severity": "INFO"}
</Callout>

This is useful in a number of use cases including:
- Internal logging: Forward all events into your organization’s centralized logging pipeline.
Expand Down
Loading
Loading