Clerk new user notifications on your phone.

Get a low-priority push notification when someone signs up for your app through Clerk, with their name, email and how they signed up.

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 Clerk

Add it as a webhook for user.created.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the Clerk user.created 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 the Clerk Dashboard, open Webhooks and click Add Endpoint.
  3. Paste your webhook URL as the Endpoint URL and subscribe to user.created.https://••••••••/•••••••• your personal URL, shown after install
  4. Click Create. The endpoint's Testing tab can send an example event.
  5. In the Clerk Dashboard, open **Webhooks → Add Endpoint** and subscribe to **user.created** only. Webhooks are set up per instance, so add the endpoint to your production instance (and to development too if you want test sign-ups).
  6. The push shows the user's name, primary email (or phone) and how they signed up: the social provider (such as Google), SSO, a Web3 wallet, a password, or an email or phone code. Users you create in the Dashboard or through the API also push.
  7. Use the endpoint's **Testing** tab and **Send Example** to try it.
  8. Clerk sends webhooks through Svix and signs every delivery (`svix-id`, `svix-timestamp`, `svix-signature`), but JustPush doesn't check that signature yet, so keep your endpoint URL private.
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// Clerk → user.created
2// Fires when someone signs up for your app, or a user is created in the Clerk Dashboard or API.
3// Docs: https://clerk.com/docs/guides/development/webhooks/overview
4
5// Clerk's external account providers ("oauth_google") with nicer names; others are title-cased.
6const PROVIDERS = {
7 oauth_github: "GitHub",
8 oauth_gitlab: "GitLab",
9 oauth_linkedin: "LinkedIn",
10 oauth_linkedin_oidc: "LinkedIn",
11 oauth_tiktok: "TikTok",
12 oauth_x: "X",
13 oauth_hubspot: "HubSpot",
14}
15
16// Email verification strategies that tell us how a password-less user signed up.
17const STRATEGIES = {
18 email_code: "an email code",
19 email_link: "an email link",
20 phone_code: "a phone code",
21}
22
23// Look a key up without tripping over inherited names like "toString".
24function pick(table, key, fallback) {
25 return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
26}
27
28// Only link to real http(s) URLs.
29function link(url, cta) {
30 return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : []
31}
32
33// A trimmed string, or null.
34function text(value) {
35 return typeof value === "string" && value.trim() ? value.trim() : null
36}
37
38// A list, or an empty one.
39function list(value) {
40 return Array.isArray(value) ? value.filter((x) => x && typeof x === "object") : []
41}
42
43// "oauth_google" → "Google", "oauth_apple" → "Apple"
44function providerName(provider) {
45 const p = text(provider)
46 if (!p) return null
47 const known = pick(PROVIDERS, p, null)
48 if (known) return known
49 const name = p.replace(/^oauth_/, "").replace(/_/g, " ")
50 return name.charAt(0).toUpperCase() + name.slice(1)
51}
52
53// How the user signed up, as far as the payload tells us.
54function method(user, primaryEmail) {
55 const social = providerName(list(user.external_accounts)[0]?.provider)
56 if (social) return social
57 if (list(user.enterprise_accounts).length) return "SSO"
58 if (list(user.web3_wallets).length) return "a Web3 wallet"
59 if (user.password_enabled === true) return "a password"
60 return pick(STRATEGIES, primaryEmail?.verification?.strategy, null)
61}
62
63function handleRequest(request) {
64 const body = request.body && typeof request.body === "object" ? request.body : {}
65 const user = body.data && typeof body.data === "object" ? body.data : null
66
67 // Guard: only user.created, so other Clerk events on this integration stay quiet.
68 if (body.type !== "user.created" || !user) {
69 console.log(`Ignoring Clerk "${body.type ?? "unknown"}" event`)
70 return null
71 }
72
73 const emails = list(user.email_addresses)
74 const primary = emails.find((e) => e.id === user.primary_email_address_id) ?? emails[0]
75 const email = text(primary?.email_address)
76 const phones = list(user.phone_numbers)
77 const phone = text((phones.find((p) => p.id === user.primary_phone_number_id) ?? phones[0])?.phone_number)
78 const name = [text(user.first_name), text(user.last_name)].filter(Boolean).join(" ") || text(user.username)
79 const contact = email ?? phone
80
81 const who = name && contact ? `${name} (${contact})` : name ?? contact ?? "Someone"
82 const how = method(user, primary)
83
84 return {
85 title: "👤 New user",
86 message: `${who} signed up${how ? ` with ${how}` : ""}`,
87 topic: "Clerk",
88 priority: -1, // Low: good news, nothing to do
89 // "~" opens the Dashboard's current application; the deep link isn't in Clerk's webhook docs.
90 buttons: text(user.id) ? link(`https://dashboard.clerk.com/~/users/${encodeURIComponent(user.id)}`, "View user") : [],
91 }
92}
Payload

The Clerk user.created webhook

This is what Clerk sends to your URL for user.created. It is a sample, trimmed to the fields recipes use.

user.created · sample.json
1{
2 "data": {
3 "id": "user_29w83sxmDNGwOuEthce5gg56FcC",
4 "banned": false,
5 "object": "user",
6 "username": null,
7 "has_image": true,
8 "image_url": "https://img.clerk.com/example",
9 "last_name": "Lee",
10 "created_at": 1790670591514,
11 "first_name": "Sam",
12 "updated_at": 1790670591835,
13 "external_id": null,
14 "web3_wallets": [],
15 "phone_numbers": [],
16 "email_addresses": [
17 {
18 "id": "idn_29w83yL7CwVlJXylYLxcslromF1",
19 "object": "email_address",
20 "linked_to": [
21 {
22 "id": "idn_29w83yL7CwVlJXylYLxcslromF2",
23 "type": "oauth_google"
24 }
25 ],
26 "verification": {
27 "status": "verified",
28 "strategy": "from_oauth_google"
29 },
30 "email_address": "[email protected]"
31 }
32 ],
33 "last_sign_in_at": 1790670591514,
34 "public_metadata": [],
35 "unsafe_metadata": [],
36 "password_enabled": false,
37 "private_metadata": [],
38 "external_accounts": [
39 {
40 "id": "idn_29w83yL7CwVlJXylYLxcslromF2",
41 "object": "external_account",
42 "provider": "oauth_google",
43 "username": null,
44 "last_name": "Lee",
45 "first_name": "Sam",
46 "verification": {
47 "status": "verified",
48 "strategy": "oauth_google"
49 },
50 "email_address": "[email protected]",
51 "approved_scopes": "email openid profile",
52 "provider_user_id": "100000000000000000000",
53 "identification_id": "idn_29w83yL7CwVlJXylYLxcslromF2"
54 }
55 ],
56 "two_factor_enabled": false,
57 "backup_code_enabled": false,
58 "enterprise_accounts": [],
59 "primary_web3_wallet_id": null,
60 "primary_phone_number_id": null,
61 "primary_email_address_id": "idn_29w83yL7CwVlJXylYLxcslromF1"
62 },
63 "type": "user.created",
64 "object": "event",
65 "timestamp": 1790670591835,
66 "instance_id": "ins_123",
67 "event_attributes": {
68 "http_request": {
69 "client_ip": "192.0.2.10",
70 "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"
71 }
72 }
73}
FAQ

Clerk new user 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 Clerk 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

Clerk on your phone in two minutes.

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