Changelog

Integrations

  • Five destinations besides raw webhooks — Slack, Discord, Loops, PostHog and Kit
  • Configure any of them from the dashboard, or over /v1/integrations
  • Typed config per destination in the SDK, so the wrong shape is a compile error
  • Pause an integration without losing its config, secret or delivery history

Outbound webhooks used to be the only way to hear about a signup without polling, and a webhook is a server you have to write. There are now five destinations that need no server at all: Slack, Discord, Loops, PostHog and Kit. Each is configured with its own credentials, subscribed to whichever event types you pick, and delivered by the same machinery that always carried the raw webhook.

Slack posts a Block Kit message per event — the address, its position, the size of the list, and the first few metadata values. Discord posts an embed with the same facts. Both take a webhook URL, which is write-only once saved.

Loops creates or updates a contact, optionally on a mailing list, and stamps the Enlist position and waitlist id on it. It ignores signup.email_delivered and signup.email_opened on purpose — Loops records those from its own sends, so forwarding ours would double-count them.

PostHog captures an event per subscribed type against your project key, with the position, total, referral code and utm source as properties, and the signup's metadata as person properties. EU cloud and self-hosted instances work; a host on a private range does not, for the same reason a webhook URL on one does not.

Kit subscribes the address to a form or straight to the account, applies your tags, and on removal untags or unsubscribes rather than deleting — a Kit delete is account-wide and takes their whole history with it.

Every destination is reachable from the dashboard, and every one of them is also reachable over /v1/integrations: PUT to configure, PATCH to pause or resume, DELETE to discard, and GET /v1/integrations/:type/deliveries for the last fifty attempts with the status each one got. The SDK types the config per destination, so a Slack config passed to put('discord', …) fails to compile rather than coming back a 400.

Config is never returned by anything. Secret fields omitted from a PUT keep their stored value, which is what makes it possible to change a URL without resending a credential you cannot read back. A key the destination does not declare is a 400 naming it — Unknown config field: apiKey. — not a silent drop, because config is stored as sent and read back on every delivery, so a typo that saved quietly would go on being wrong for as long as the integration lived.

Integrations are a Launch feature, and now on every path rather than only in the dashboard: PUT /v1/integrations/:type and a resume answer 402 quota_exceeded on Free, and delivery stops when an account downgrades rather than running on from a config that outlived the plan. Listing, pausing and deleting stay open on every plan — an account that stops paying still has to be able to see what it set up and switch it off.

Pausing and deleting are different operations and only one is reversible. PATCH with enabled: false stops delivery and keeps the config, the signing secret and the delivery log; resuming needs nothing re-entered. DELETE destroys all of it, and a webhook added back gets a new signing secret every receiver has to be updated with.

Nothing about the existing outbound webhook changed for anyone consuming it. Same URL, same signing secret, same enlist-signature header, same best-effort delivery with no retries. It is now one integration type among six rather than its own subsystem, which is what let the delivery log and the pause switch it already had become things every destination has. Sending a test event is still webhook-only: the other five deliver to an account you can go and look at.

Latest updates

Integrations · Enlist