Kit unsubscribe notifications on your phone.

Get a low-priority push notification when someone unsubscribes from your Kit newsletter, and a louder one when they report your email as spam.

subscriber.unsubscribedEmail marketingNewsletters
How it works

Three steps to your first push

STEP 01

Install the recipe

One click in Studio gives you a personal webhook URL.

STEP 02

Paste your URL into Kit

Add it as a webhook for Subscriber unsubscribed.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the Kit subscriber unsubscribed webhook

  1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
  2. In Kit, open app.kit.com/webhooks and create a webhook endpoint.
  3. Paste your webhook URL as the URL.https://••••••••/•••••••• your personal URL, shown after install
  4. Subscribe it to Subscriber unsubscribed only and save.
  5. Subscribe the endpoint to `subscriber.unsubscribed`, and add `subscriber.complained` if you also want to hear about spam complaints.
  6. You can also create the endpoint through the API:
  7. ```bash
  8. curl -X POST https://api.kit.com/v4/webhook_endpoints \
  9. -H "X-Kit-Api-Key: <your-api-key>" \
  10. -H "Content-Type: application/json" \
  11. -d '{ "url": "your webhook URL", "events": ["subscriber.unsubscribed", "subscriber.complained"] }'
  12. ```
  13. Kit signs each delivery (`X-Kit-Signature`), but JustPush doesn't check that signature yet, so keep your endpoint URL private. Webhooks need a Kit plan with API access.
The code

What runs when the webhook arrives

Studio calls handleRequest(request) with the incoming webhook and sends the message it returns. It's yours after install; change anything.

transform.js
1// Kit → subscriber.unsubscribed
2// Fires when a subscriber unsubscribes in Kit. Also handles subscriber.complained
3// (they reported your email as spam), which gets a louder push.
4// Docs: https://developers.kit.com/webhooks/event-types
5//
6// Kit's webhook endpoints send { delivery_id, events: [ … ] } with up to 100 events of
7// one type per delivery. Legacy webhooks (/v4/webhooks) send a bare { subscriber: { … } }.
8
9// Event types this recipe handles, and the legacy subscriber state that matches each.
10const TYPES = {
11 "subscriber.unsubscribed": "cancelled",
12 "subscriber.complained": "complained",
13}
14
15// Kit sends plain strings; treat blanks as missing.
16function text(value) {
17 return typeof value === "string" && value.trim() ? value.trim() : null
18}
19
20// "Ada ([email protected])", or whichever half is there.
21function describe(subscriber) {
22 const name = text(subscriber?.first_name)
23 const email = text(subscriber?.email_address)
24 return name && email ? `${name} (${email})` : name || email || "Someone"
25}
26
27function handleRequest(request) {
28 const body = request.body && typeof request.body === "object" ? request.body : {}
29 const has = (key) => Object.prototype.hasOwnProperty.call(TYPES, key)
30
31 let type = null
32 let subscribers = []
33 if (Array.isArray(body.events)) {
34 // Webhook endpoints: every event in a delivery has the same type.
35 const events = body.events.filter((e) => has(e?.type) && e.data?.subscriber)
36 type = events[0]?.type ?? null
37 subscribers = events.filter((e) => e.type === type).map((e) => e.data.subscriber)
38 } else if (body.subscriber && typeof body.subscriber === "object") {
39 // Legacy webhooks carry no event name; the subscriber's state tells us what happened.
40 type = Object.keys(TYPES).find((key) => TYPES[key] === body.subscriber.state) ?? null
41 subscribers = type ? [body.subscriber] : []
42 }
43
44 // Guard: nothing this recipe handles, e.g. a new subscriber or a tag event.
45 if (!type || !subscribers.length) {
46 console.log(`Ignoring Kit "${body.events?.[0]?.type ?? body.subscriber?.state ?? "unknown"}" delivery`)
47 return null
48 }
49
50 const spam = type === "subscriber.complained"
51 const who =
52 subscribers.length === 1
53 ? describe(subscribers[0])
54 : subscribers.slice(0, 3).map((s) => text(s.email_address) ?? "someone").join(", ") +
55 (subscribers.length > 3 ? ` and ${subscribers.length - 3} more` : "")
56
57 return {
58 title: spam
59 ? subscribers.length > 1 ? `⚠️ ${subscribers.length} spam complaints` : "⚠️ Spam complaint"
60 : subscribers.length > 1 ? `👋 ${subscribers.length} unsubscribes` : "👋 Unsubscribed",
61 message: spam ? `${who} reported your email as spam` : `${who} unsubscribed`,
62 topic: "Kit",
63 priority: spam ? 0 : -1, // Normal for a spam complaint (hurts deliverability), Low for routine churn
64 }
65}
Payload

The Kit subscriber.unsubscribed webhook

This is what Kit sends to your URL for subscriber unsubscribed. It is a sample, trimmed to the fields recipes use.

subscriber.unsubscribed · sample.json
1{
2 "events": [
3 {
4 "id": "1f0b6c2d-3e4a-4b5c-8d9e-0a1b2c3d4e5f",
5 "data": {
6 "subscriber": {
7 "id": 987654,
8 "state": "cancelled",
9 "fields": [],
10 "created_at": "2026-06-12T08:15:00Z",
11 "first_name": "Ada",
12 "email_address": "[email protected]"
13 }
14 },
15 "type": "subscriber.unsubscribed",
16 "created": "2026-09-29T10:02:44Z"
17 }
18 ],
19 "delivery_id": 123457
20}
FAQ

Kit unsubscribe notifications: questions

Does this work on iPhone and Android?

Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification.

Do I need to write code?

No. Install the recipe in Studio, paste your webhook URL into Kit and you are done. The code is there if you want to change the text, the sound or the buttons.

Can I change what the notification says?

Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save.

What does it cost?

JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.

Ready when you are

Kit on your phone in two minutes.

Install the recipe, paste one URL, done. Free for 30 days, no credit card required.