Skip to content
Nowlark
API reference menu

Destinations: webhooks and email

A channel always reaches its members' phones. A destination adds somewhere else: a webhook the team runs (to open a ticket, post in chat, start a script) or an email address (for the people who will not install an app, which is most small businesses receiving their website's enquiries). Both are fed from the same places the phones are, and each channel may have 10.

curl -X POST https://nowlark.com/v1/channels/chn_…/destinations -H "Authorization: Bearer nla_…" \
  -H 'Content-Type: application/json' \
  -d '{"kind":"webhook","target":"https://hooks.yourcompany.com/nowlark","events":["thread.created","thread.acknowledged"]}'

Events: thread.created (an alert arrived), thread.acknowledged (someone opened it, or a form submission was claimed), thread.escalated (a later tier was notified), thread.unopened (nobody took it: still unopened a set time after the last tier was notified) and thread.resolved (forms: a submission was marked replied or archived). Default is the first three, and destinations made before the last two existed keep the three; name the others in events to get them. A test event is sent by the test endpoint.

Nobody took it. thread.unopened is sent once if, when the wait is over, nobody on the channel has opened, taken or answered the alert. The wait starts when the last escalation tier is notified, or, on a channel with no escalation, when the alert is sent. With repeat, that is the last tier of the last repeat. The default is 900 seconds (15 minutes), longer than a person's own 5-minute email so that each of them has had their chance first; set it per channel with PATCH /v1/channels/{id} {"unopened_after": 600} (300, 600, 900, 1800, 3600 or 7200, or null for the default).

Delivery. Each event is a row in an outbox, one per (destination, thread, event) for ever, so a send retried after a crash does not deliver twice. A failure is retried after 30 s, 5 min, 30 min, 2 h and 12 h, then given up on. A destination whose deliveries fail 20 times in a row is switched off (disabled in the list, with the reason) so a dead endpoint stops costing a retry schedule per alert; …/enable switches it back on. The acknowledged event is read from the members' copies by a sweeper, so every way of opening one reaches it. Delivered within one scheduler tick (15 seconds).

Webhook body and signature. JSON: event, id (the thread), team and channel (id, name), at, and for thread.created the source, title, body, url, priority and tags; for thread.acknowledged the earliest acknowledged_at and acknowledged_by, the names members chose for the team (never an account id); for thread.escalated the tier and notified; for thread.unopened the source, title and waited (seconds since the alert was sent, not since the last tier); for thread.resolved the source, title, status and by. Headers: X-Nowlark-Event, X-Nowlark-Delivery (use it to deduplicate) and

X-Nowlark-Signature: t=1789665612,v1=5257a869e7ec…

where v1 is the hex HMAC-SHA256, keyed with the destination's secret (whsec_…, shown once, when it is created), of <t>.<raw body>. Recompute it, compare in constant time, and reject a t more than a few minutes old. The URL is held to every rule that applies to a callback_url: https only, public addresses only, no redirects, 5 s timeout.

Email. Nothing is sent to an address until it has followed a link sent to it: adding one sends a verification email (3 per address and 20 per team a day), and until it is confirmed nothing else is queued. The link opens a page with a button, because mail scanners open every link; only the button confirms. Every email carries a one-click unsubscribe (List-Unsubscribe with List-Unsubscribe-Post), and an address that uses it is remembered, by hash, across every channel and team: it cannot be added again and an admin cannot switch it back on. An address receives at most 60 emails an hour and a team 500 a day; what exceeds that waits and is sent later, it is not lost. The subject is one line whatever a sender put in the title, and only a plain address is accepted (no name, no list, no line break). Email needs the operator to configure a relay; without one, adding an email destination is 409 email_not_configured.

Bounces. What the relay answers at send time decides what happens. An address it says does not exist (an enhanced 5.1.x status, or 550, 551 or 553 with no enhanced status) is a hard bounce: the delivery fails at once and is never retried, the address is suppressed for every channel and team, and every destination for it is switched off with the reason (address bounced (550)). A 4xx or a network failure is soft and is retried on the usual schedule. Any other 5xx (blocked as spam, mailbox full) is a refusal about the relay or the content, not the address: no retry and no suppression, but it counts toward the destination's run of failures, so a relay that refuses everything switches the channel's email off. A bounced or complained-about address cannot be switched back on by an admin.

Mail the relay accepted can still bounce later. Ask support to connect your relay's bounce reports.