# 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. - Company: Clerk (https://www.justpush.io/recipes/clerk) - Event: `user.created` (user.created) - Tags: Backend & auth, Sign-ups & users - Install: https://studio.justpush.io/recipes/clerk/new-user - Web page: https://www.justpush.io/recipes/clerk/new-user ## Setup 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 (shown in Studio after install) as the Endpoint URL and subscribe to user.created. 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. ## Code Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing. ```js // Clerk → user.created // Fires when someone signs up for your app, or a user is created in the Clerk Dashboard or API. // Docs: https://clerk.com/docs/guides/development/webhooks/overview // Clerk's external account providers ("oauth_google") with nicer names; others are title-cased. const PROVIDERS = { oauth_github: "GitHub", oauth_gitlab: "GitLab", oauth_linkedin: "LinkedIn", oauth_linkedin_oidc: "LinkedIn", oauth_tiktok: "TikTok", oauth_x: "X", oauth_hubspot: "HubSpot", } // Email verification strategies that tell us how a password-less user signed up. const STRATEGIES = { email_code: "an email code", email_link: "an email link", phone_code: "a phone code", } // Look a key up without tripping over inherited names like "toString". function pick(table, key, fallback) { return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback } // Only link to real http(s) URLs. function link(url, cta) { return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : [] } // A trimmed string, or null. function text(value) { return typeof value === "string" && value.trim() ? value.trim() : null } // A list, or an empty one. function list(value) { return Array.isArray(value) ? value.filter((x) => x && typeof x === "object") : [] } // "oauth_google" → "Google", "oauth_apple" → "Apple" function providerName(provider) { const p = text(provider) if (!p) return null const known = pick(PROVIDERS, p, null) if (known) return known const name = p.replace(/^oauth_/, "").replace(/_/g, " ") return name.charAt(0).toUpperCase() + name.slice(1) } // How the user signed up, as far as the payload tells us. function method(user, primaryEmail) { const social = providerName(list(user.external_accounts)[0]?.provider) if (social) return social if (list(user.enterprise_accounts).length) return "SSO" if (list(user.web3_wallets).length) return "a Web3 wallet" if (user.password_enabled === true) return "a password" return pick(STRATEGIES, primaryEmail?.verification?.strategy, null) } function handleRequest(request) { const body = request.body && typeof request.body === "object" ? request.body : {} const user = body.data && typeof body.data === "object" ? body.data : null // Guard: only user.created, so other Clerk events on this integration stay quiet. if (body.type !== "user.created" || !user) { console.log(`Ignoring Clerk "${body.type ?? "unknown"}" event`) return null } const emails = list(user.email_addresses) const primary = emails.find((e) => e.id === user.primary_email_address_id) ?? emails[0] const email = text(primary?.email_address) const phones = list(user.phone_numbers) const phone = text((phones.find((p) => p.id === user.primary_phone_number_id) ?? phones[0])?.phone_number) const name = [text(user.first_name), text(user.last_name)].filter(Boolean).join(" ") || text(user.username) const contact = email ?? phone const who = name && contact ? `${name} (${contact})` : name ?? contact ?? "Someone" const how = method(user, primary) return { title: "👤 New user", message: `${who} signed up${how ? ` with ${how}` : ""}`, topic: "Clerk", priority: -1, // Low: good news, nothing to do // "~" opens the Dashboard's current application; the deep link isn't in Clerk's webhook docs. buttons: text(user.id) ? link(`https://dashboard.clerk.com/~/users/${encodeURIComponent(user.id)}`, "View user") : [], } } ``` ## Clerk user.created webhook payload (sample) ```json { "data": { "id": "user_29w83sxmDNGwOuEthce5gg56FcC", "banned": false, "object": "user", "username": null, "has_image": true, "image_url": "https://img.clerk.com/example", "last_name": "Lee", "created_at": 1790670591514, "first_name": "Sam", "updated_at": 1790670591835, "external_id": null, "web3_wallets": [], "phone_numbers": [], "email_addresses": [ { "id": "idn_29w83yL7CwVlJXylYLxcslromF1", "object": "email_address", "linked_to": [ { "id": "idn_29w83yL7CwVlJXylYLxcslromF2", "type": "oauth_google" } ], "verification": { "status": "verified", "strategy": "from_oauth_google" }, "email_address": "sam@example.com" } ], "last_sign_in_at": 1790670591514, "public_metadata": [], "unsafe_metadata": [], "password_enabled": false, "private_metadata": [], "external_accounts": [ { "id": "idn_29w83yL7CwVlJXylYLxcslromF2", "object": "external_account", "provider": "oauth_google", "username": null, "last_name": "Lee", "first_name": "Sam", "verification": { "status": "verified", "strategy": "oauth_google" }, "email_address": "sam@example.com", "approved_scopes": "email openid profile", "provider_user_id": "100000000000000000000", "identification_id": "idn_29w83yL7CwVlJXylYLxcslromF2" } ], "two_factor_enabled": false, "backup_code_enabled": false, "enterprise_accounts": [], "primary_web3_wallet_id": null, "primary_phone_number_id": null, "primary_email_address_id": "idn_29w83yL7CwVlJXylYLxcslromF1" }, "type": "user.created", "object": "event", "timestamp": 1790670591835, "instance_id": "ins_123", "event_attributes": { "http_request": { "client_ip": "192.0.2.10", "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" } } } ``` ## FAQ ### 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.