<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Phil Leggetter</title>
    <description>The latest articles on Forem by Phil Leggetter (@leggetter).</description>
    <link>https://forem.com/leggetter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F251120%2F5a0569f4-d0ae-4cea-b5e7-8391f608c21a.jpeg</url>
      <title>Forem: Phil Leggetter</title>
      <link>https://forem.com/leggetter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/leggetter"/>
    <language>en</language>
    <item>
      <title>How to Make Your AI Agent Get Webhooks Right (A Guide to Webhook Skills)</title>
      <dc:creator>Phil Leggetter</dc:creator>
      <pubDate>Wed, 18 Feb 2026 15:27:46 +0000</pubDate>
      <link>https://forem.com/hookdeck/how-to-make-your-ai-agent-get-webhooks-right-a-guide-to-webhook-skills-17im</link>
      <guid>https://forem.com/hookdeck/how-to-make-your-ai-agent-get-webhooks-right-a-guide-to-webhook-skills-17im</guid>
      <description>&lt;p&gt;When I ask my AI coding agent to set up webhooks from a new API (Stripe, Shopify, GitHub, whatever), the code it generates often looks fine until I run it. Then I hit signature verification failures, wrong raw body handling, or idempotency bugs that process the same event twice. Sound familiar?&lt;/p&gt;

&lt;p&gt;I created &lt;a href="https://github.com/hookdeck/webhook-skills" rel="noopener noreferrer"&gt;&lt;strong&gt;webhook-skills&lt;/strong&gt;&lt;/a&gt; to fix that. They're agent skills for webhooks: an open-source collection of provider- and framework-specific instructions and runnable examples that AI coding agents can load so they implement webhooks correctly the first time. In this guide I'll walk through how to use them, what's in them, and how you can contribute or request skills that are missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Agents Get Webhooks Wrong
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://hookdeck.com/" rel="noopener noreferrer"&gt;Hookdeck&lt;/a&gt; we process billions of webhooks a week, and we've seen every failure mode: signature mismatches from body parsing middleware, framework-specific gotchas that only show up in production, and so on. AI agents struggle with the same things. Their training data goes stale quickly. API versions change, security practices evolve, and the details that matter (raw body handling, middleware order, encoding) aren't in the model.&lt;/p&gt;

&lt;p&gt;Research from PostHog on &lt;a href="https://posthog.com/blog/correct-llm-code-generation" rel="noopener noreferrer"&gt;LLM code generation&lt;/a&gt; backs this up: the most reliable way to get correct code isn't the model's general knowledge; it's giving it specific, known-working examples to reference. Webhook skills are built to fill that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Webhook Skills Fix It
&lt;/h2&gt;

&lt;p&gt;Webhook-skills is built on the &lt;a href="https://github.com/agent-skills/spec" rel="noopener noreferrer"&gt;Agent Skills specification&lt;/a&gt;, an open standard for packaging knowledge that agents can consume. In practice that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runnable examples:&lt;/strong&gt; complete, minimal apps the agent can reference and adapt, not just snippets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider-specific guidance:&lt;/strong&gt; Stripe's raw body requirement, Shopify's HMAC encoding, and other gotchas we see trip up developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework-aware implementations:&lt;/strong&gt; how Next.js, Express, and FastAPI handle request bodies, middleware order, and async patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staged workflows:&lt;/strong&gt; verify signature first, parse payload second, handle idempotently third.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I ask my agent to "add Stripe webhooks to my Next.js app," it doesn't hallucinate a generic handler. It has the exact patterns for App Router body handling, preserving the raw body before verification, and pulling the webhook secret from env with the right naming. That's the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use Them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install the skills
&lt;/h3&gt;

&lt;p&gt;I use &lt;code&gt;npx skills&lt;/code&gt; to add webhook-skills to my project. First I list what's available, then I install the skills I need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List available skills&lt;/span&gt;
npx skills add hookdeck/webhook-skills &lt;span class="nt"&gt;--list&lt;/span&gt;

&lt;span class="c"&gt;# Install best-practice patterns (verify → parse → handle idempotently)&lt;/span&gt;
npx skills add hookdeck/webhook-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; webhook-handler-patterns

&lt;span class="c"&gt;# Install specific provider skills&lt;/span&gt;
npx skills add hookdeck/webhook-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; stripe-webhooks
npx skills add hookdeck/webhook-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; shopify-webhooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I usually install &lt;code&gt;webhook-handler-patterns&lt;/code&gt; plus the provider skill for whichever API I'm integrating. That gives my agent both the general flow and the provider-specific details.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to prompt
&lt;/h3&gt;

&lt;p&gt;Once the skills are installed, I prompt naturally. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Add Shopify webhook handling to my Express app."&lt;/li&gt;
&lt;li&gt;"Set up Stripe webhooks in my Next.js app."&lt;/li&gt;
&lt;li&gt;"Implement GitHub webhook verification in my FastAPI service."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent has the patterns; I don't need to spell out raw body or signature verification. It already knows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local testing (optional)
&lt;/h3&gt;

&lt;p&gt;When I'm testing webhooks locally, I use the &lt;a href="https://github.com/hookdeck/hookdeck-cli" rel="noopener noreferrer"&gt;Hookdeck CLI&lt;/a&gt;. It gives me a public URL that tunnels to my local server and a UI to inspect and replay requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; hookdeck-cli
&lt;span class="c"&gt;# or: brew install hookdeck/hookdeck/hookdeck&lt;/span&gt;

hookdeck listen 3000 &lt;span class="nt"&gt;--path&lt;/span&gt; /webhooks/stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No account required to get started. The skills work with or without Hookdeck; they're just complementary when you want to receive real webhooks on localhost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Available (and What's Missing)
&lt;/h2&gt;

&lt;p&gt;Right now webhook-skills covers the providers and frameworks we see most often:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Providers:&lt;/strong&gt; &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/shopify-webhooks" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt;, &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/github-webhooks" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/openai-webhooks" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/resend-webhooks" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/paddle-webhooks" rel="noopener noreferrer"&gt;Paddle&lt;/a&gt;, &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/elevenlabs-webhooks" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt;, &lt;a href="https://github.com/hookdeck/webhook-skills/tree/main/skills/chargebee-webhooks" rel="noopener noreferrer"&gt;Chargebee&lt;/a&gt;, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frameworks:&lt;/strong&gt; Next.js, Express, and FastAPI.&lt;/p&gt;

&lt;p&gt;Each provider skill includes signature verification, event handling guidance, common failure modes, and testing tips for local dev. Coverage isn't complete yet. If you need a provider we don't have or you want to contribute, see the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Contribute and Ask for Skills
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Request a skill:&lt;/strong&gt; If you're integrating a provider we don't support yet, &lt;a href="https://github.com/hookdeck/webhook-skills/issues" rel="noopener noreferrer"&gt;open an issue on GitHub&lt;/a&gt; with a title like &lt;strong&gt;"Skill request: [Provider] webhooks"&lt;/strong&gt;. Describe the provider and your framework if relevant. I use these to prioritize what to add next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribute a skill:&lt;/strong&gt; If you've built a webhook integration you're proud of, PRs for new providers or frameworks are welcome. The repo is &lt;a href="https://github.com/hookdeck/webhook-skills" rel="noopener noreferrer"&gt;hookdeck/webhook-skills&lt;/a&gt;. Check the existing skills for structure and open a PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API and platform maintainers:&lt;/strong&gt; If you maintain a webhook-producing API and want AI coding agents to implement your webhooks correctly out of the box, I'd love a skill from you. Open an issue or PR and we can align on format and content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give It a Shot
&lt;/h2&gt;

&lt;p&gt;If you've ever lost an afternoon to webhook signature verification or body parsing, try webhook-skills the next time you're wiring up an integration. Install the skills, prompt your agent, and you might find it finally does what you meant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hookdeck/webhook-skills" rel="noopener noreferrer"&gt;webhook-skills on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hookdeck/hookdeck-cli" rel="noopener noreferrer"&gt;Hookdeck CLI&lt;/a&gt; (optional, for local webhook testing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have feedback or want to request or contribute a skill? &lt;a href="https://github.com/hookdeck/webhook-skills/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introducing The Event Destinations Initiative</title>
      <dc:creator>Phil Leggetter</dc:creator>
      <pubDate>Thu, 27 Feb 2025 13:57:21 +0000</pubDate>
      <link>https://forem.com/hookdeck/introducing-the-event-destinations-initiative-4065</link>
      <guid>https://forem.com/hookdeck/introducing-the-event-destinations-initiative-4065</guid>
      <description>&lt;p&gt;The Event Destinations Initiative is a community effort aimed at creating a model for event interoperability between event producers and their consumers. This initiative focuses on providing a set of guidelines for managing event destinations, ensuring seamless integration, improved reliability, greater event delivery choice, and a much-improved developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Event Destinations?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hookdeck.com/blog/event-destinations" rel="noopener noreferrer"&gt;Event Destinations&lt;/a&gt; are the endpoints where API platform events are delivered and consumed. Event destinations can include webhook endpoints, message queues, APIs, &lt;a href="https://hookdeck.com/blog/event-destinations/blog/event-gateway-definition" rel="noopener noreferrer"&gt;event gateways&lt;/a&gt;, and other services that handle event data. Webhooks are the most ubiquitous form of event destination, but the Event Destinations Initiative aims to establish a set of guidelines for API platforms to provide additional event delivery options offering more choice, improved reliability at scale, and an overall better developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Event Destinations Initiative?
&lt;/h2&gt;

&lt;p&gt;The Event Destinations Initiative is a collaborative project that aims to create a set of guidelines for defining, managing, and utilizing event destinations across platforms and services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardization&lt;/strong&gt;: Establishing a common set of guidelines and best practices for event destinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interoperability&lt;/strong&gt;: Ensuring compatibility across different platforms and services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Providing solutions that can scale with the growing demands of modern applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;: Enhancing the reliability and fault tolerance of event-driven architectures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Integration&lt;/strong&gt;: Easier integration with commonly used services and platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Efficiency&lt;/strong&gt;: Streamlined event publishing, routing, and ingestion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Reliability&lt;/strong&gt;: More robust and fault-tolerant event handling for both the publisher and consumer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future-Proofing&lt;/strong&gt;: A forward-looking approach that anticipates future needs and challenges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Supporters are Saying
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;In his book Flow Architectures, James Urquhart envisions a future where APIs are inherently asynchronous, with synchronous APIs being reserved only for scenarios where they are truly necessary.&lt;/p&gt;

&lt;p&gt;The Event Destinations Initiative represents a significant step toward realizing that vision. Enabling API producers to adopt the most efficient and performant methods for sending and receiving asynchronous, non-blocking data is a development I wholeheartedly support—particularly when it is driven by a collaborative and open effort.&lt;/p&gt;

&lt;p&gt;This direction strongly aligns with the vision we champion at AsyncAPI, and the synergy between these efforts is both promising and exciting. I am eager to see the Event Destinations Initiative succeed and contribute to the evolution and blending of API and EDA ecosystems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-- &lt;strong&gt;Fran Méndez, Founder @ AsyncAPI Initiative&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've been working with (and often against) webhooks for years, both as a consumer and an implementer, always wishing that we as an industry could come up with something better. In my opinion, Event Destinations are exactly that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-- &lt;strong&gt;Paul Asjes, Developer Experience Engineer @ ElevenLabs&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;We invite developers, architects, and organizations to join us in this initiative. By collaborating, we can build a more efficient and reliable event-driven ecosystem.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://eventdestinations.org/" rel="noopener noreferrer"&gt;eventdestinations.org&lt;/a&gt; to learn more and get involved.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>eventdriven</category>
      <category>pubsub</category>
      <category>api</category>
    </item>
    <item>
      <title>Free ngrok alternative for async web dev - the Hookdeck CLI</title>
      <dc:creator>Phil Leggetter</dc:creator>
      <pubDate>Mon, 29 Jul 2024 18:31:55 +0000</pubDate>
      <link>https://forem.com/hookdeck/free-ngrok-alternative-for-async-web-dev-the-hookdeck-cli-5f6j</link>
      <guid>https://forem.com/hookdeck/free-ngrok-alternative-for-async-web-dev-the-hookdeck-cli-5f6j</guid>
      <description>&lt;p&gt;ngrok is great. It's been the go-to for many a web developer who wants to be able to expose a locally running web application to the public Internet for many years. And it's still a great solution. However, ngrok has changed in ways that have resulted in a much poorer developer experience and there are now better tools for some of the jobs that ngrok was originally the go-to for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's changed with ngrok?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ngrok used to be free and only required an account for &lt;a href="https://github.com/inconshreveable/ngrok/blob/64ebca99eb355cae5a5498c76f8a318a782a0442/README.md#downloading-and-installing-ngrok" rel="noopener noreferrer"&gt;"premium features"&lt;/a&gt;. Now, you need an ngrok account no matter your use case.&lt;/li&gt;
&lt;li&gt;There's a 1GB limit a month on data transfer (in all fairness, this should be more than enough for development)&lt;/li&gt;
&lt;li&gt;The free ephemeral/random domains (the domain name changes every time you restart ngrok) have always been a bit of a pain. The first paid plan used to come with 3 subdomains but now only comes with one, making exposing multiple services at the same time more costly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffud9jg4ebgo97gw6j4rv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffud9jg4ebgo97gw6j4rv.png" alt="ngrok pricing, April 2021" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhalazvjej0woz2yi14t8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhalazvjej0woz2yi14t8.png" alt="ngrok pricing, July 2024" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More broadly, Their product focus has shifted reasonably dramatically from supporting use cases such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Temporarily sharing a website that is only running on your development machine&lt;/li&gt;
&lt;li&gt;Demoing an app at a hackathon without deploying&lt;/li&gt;
&lt;li&gt;Developing any services that consume webhooks (HTTP callbacks) by allowing you to replay those requests&lt;/li&gt;
&lt;li&gt;Debugging and understanding any web service by inspecting the HTTP traffic&lt;/li&gt;
&lt;li&gt;Running networked services on machines that are firewalled off from the internet&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;To:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All-in-one API gateway, Kubernetes Ingress, DDoS protection, firewall, and global load balancing as a service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, ngrok has shifted focus away from supporting development use cases. Therefore, it's likely time to try an alternative tool that has been built to specifically support your use case.&lt;/p&gt;

&lt;p&gt;The rest of this post will show how the Hookdeck CLI can be used to receive asynchronous events (a.k.a. webhooks) from third-party services such as Stripe, Shopify, or Twilio. If you're looking for a tool that solves the other use cases, take a look at the &lt;a href="https://github.com/anderspitman/awesome-tunneling" rel="noopener noreferrer"&gt;awesome tunneling repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is synchronous web development?
&lt;/h2&gt;

&lt;p&gt;As per the title of this post, &lt;a href="https://github.com/hookdeck/hookdeck-cli" rel="noopener noreferrer"&gt;the Hookdeck CLI&lt;/a&gt; is built to support asynchronous web development. By this, I mean requests, such as webhooks from an API service, are received and proxied onto the application running locally. However, the response from the local service is not passed back to the API service that made the request.&lt;/p&gt;

&lt;p&gt;The Hookdeck CLI and the &lt;a href="https://hookdeck.com?ref-dev-to-cli" rel="noopener noreferrer"&gt;Hookdeck Event Gateway&lt;/a&gt; are built to support asynchronous messaging with event-driven applications and not the request/response paradigm. Therefore, you can't use the Hookdeck CLI to expose and serve a locally running website or request/response API. If you want to send the result of some work to another service, you should make a separate request to that service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Hookdeck CLI as a free alternative to ngrok for webhooks
&lt;/h2&gt;

&lt;p&gt;The next section walks through installing the Hookdeck CLI, creating and running a local web server (which you can skip if you already have this running), creating a localtunnel using the CLI, sending test webhook events, and replaying webhooks using the Hookdeck Console.&lt;/p&gt;

&lt;p&gt;This guide will use the Node.js runtime. If you prefer to use Python, you can run through a &lt;a href="https://guides.curiousmints.com/hello-world-guides/hookdeck-localhost-webhooks" rel="noopener noreferrer"&gt;using the Hookdeck CLI with a Python server to receive webhooks&lt;/a&gt; guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the Hookdeck CLI
&lt;/h3&gt;

&lt;p&gt;Via NPM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i hookdeck-cli &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;hookdeck/hookdeck/hookdeck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scoop bucket add hookdeck https://github.com/hookdeck/scoop-hookdeck-cli.git
scoop &lt;span class="nb"&gt;install &lt;/span&gt;hookdeck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also &lt;a href="https://github.com/hookdeck/hookdeck-cli?tab=readme-ov-file#linux-or-without-package-managers" rel="noopener noreferrer"&gt;instructions for installing on Linux&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create and run a localhost Node.js web server
&lt;/h3&gt;

&lt;p&gt;Open your terminal and navigate to a directory for your project.&lt;/p&gt;

&lt;p&gt;Install &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express&lt;/a&gt; to use as the web server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;--save&lt;/span&gt; express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;server.js&lt;/code&gt; file in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3030&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;webhook_received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ACCEPTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🪝 Server running at http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use the Hookdeck CLI to create a localtunnel
&lt;/h3&gt;

&lt;p&gt;Run the following command in a new terminal window to create a localtunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hookdeck listen 3030 my-webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will be similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Dashboard
👤 Console URL: https://api.hookdeck.com/signin/guest?token&lt;span class="o"&gt;={&lt;/span&gt;token&lt;span class="o"&gt;}&lt;/span&gt;
Sign up &lt;span class="k"&gt;in &lt;/span&gt;the Console to make your webhook URL permanent.

Sources
🔌 my-webhook URL: https://hkdk.events/wggd60fl5cxw20

Connections
my-webhook -&amp;gt; my-webhook-to-cli forwarding to /

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Ready! &lt;span class="o"&gt;(&lt;/span&gt;^C to quit&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test receiving a webhook with a cURL command
&lt;/h3&gt;

&lt;p&gt;Run the following cURL command to act as an inbound webhook, replacing the URL with the &lt;strong&gt;Event URL&lt;/strong&gt; from the Hookdeck CLI output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://hkdk.events/cnzpo680rdhejk &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message": "Hello, World!"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cURL command output will be similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;:&lt;span class="s2"&gt;"SUCCESS"&lt;/span&gt;,&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Request handled by Hookdeck. Check your dashboard to inspect the request: https://dashboard.hookdeck.com/requests/req_[id]"&lt;/span&gt;,&lt;span class="s2"&gt;"request_id"&lt;/span&gt;:&lt;span class="s2"&gt;"req_[id]"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see the terminal running the Hookdeck CLI log the inbound webhook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2024-07-09 19:06:46 &lt;span class="o"&gt;[&lt;/span&gt;200] POST http://localhost:3030/ | https://console.hookdeck.com/?event_id&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will also see the Python server log the inbound webhook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
  webhook_received: &lt;span class="s1"&gt;'2024-07-25T10:08:22.945Z'&lt;/span&gt;,
  body: &lt;span class="o"&gt;{&lt;/span&gt; message: &lt;span class="s1"&gt;'Hello, World!'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trigger a test webhook using the Hookdeck Console
&lt;/h3&gt;

&lt;p&gt;Open the &lt;strong&gt;Console URL&lt;/strong&gt; from your terminal in your browser.&lt;/p&gt;

&lt;p&gt;Choose a &lt;strong&gt;Sample Webhook Provider&lt;/strong&gt; from the list of &lt;strong&gt;Example Webhooks&lt;/strong&gt;. For example, &lt;strong&gt;Stripe&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Select a &lt;strong&gt;Sample Webhook Type&lt;/strong&gt; from the list on the right. For example, &lt;strong&gt;invoice.created&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Send&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Hookdeck Console will show the test webhook has been triggered. You can also inspect the webhook payload and the localhost web server response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90sb9hvdumpq06l19jmh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90sb9hvdumpq06l19jmh.gif" alt="Trigger test webhook, inspect webhook payload, and view the localhost server response" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see the terminal running the Hookdeck CLI log the inbound webhook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2024-07-09 19:06:46 &lt;span class="o"&gt;[&lt;/span&gt;200] POST http://localhost:3030/webhook | https://console.hookdeck.com/?event_id&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will also see the Python server log the inbound webhook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"evt_1MhUT7E0b6fckueStwy86nfu"&lt;/span&gt;,
  &lt;span class="s2"&gt;"object"&lt;/span&gt;: &lt;span class="s2"&gt;"event"&lt;/span&gt;,
  &lt;span class="s2"&gt;"api_version"&lt;/span&gt;: &lt;span class="s2"&gt;"2022-11-15"&lt;/span&gt;,
  &lt;span class="s2"&gt;"created"&lt;/span&gt;: 1677833999,
  &lt;span class="s2"&gt;"data"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    ...
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"type"&lt;/span&gt;: &lt;span class="s2"&gt;"invoice.created"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Replay a webhook
&lt;/h3&gt;

&lt;p&gt;To replay the webhook, go to the Hookdeck Console and click on the &lt;strong&gt;Resend to destination&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pmsdfbpzdddz8ne476b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pmsdfbpzdddz8ne476b.gif" alt="Replay Webhook" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Receive a webhook from an API platform
&lt;/h3&gt;

&lt;p&gt;Copy the &lt;strong&gt;Event URL&lt;/strong&gt; from the Hookdeck CLI output. You can also find the same URL in the Hookdeck Console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uiznlvquzfxtwo2p47v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uiznlvquzfxtwo2p47v.png" alt="Copy Hookdeck Event URL" width="800" height="52"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the API provider platform, such as Resend, Twilio, Shopify, or Stripe, and register the Hookdeck URL as the webhook URL with the provider.&lt;/p&gt;

&lt;p&gt;Trigger a webhook from your selected API provider to see a log entry in the Hookdeck Console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oy8hjns1zcvquhlao7s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oy8hjns1zcvquhlao7s.gif" alt="Inbound webhook from Vonage SMS API handled in the Hookdeck Console" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will also see the webhook logged in the Hookdeck CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2024-07-09 19:45:57 &lt;span class="o"&gt;[&lt;/span&gt;200] POST http://localhost:3030/webhook | https://console.hookdeck.com/?event_id&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And by the Node.js server running in your local development environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"to"&lt;/span&gt;: &lt;span class="s2"&gt;"{to_number}"&lt;/span&gt;,
  &lt;span class="s2"&gt;"from"&lt;/span&gt;: &lt;span class="s2"&gt;"{from_number}"&lt;/span&gt;,
  &lt;span class="s2"&gt;"channel"&lt;/span&gt;: &lt;span class="s2"&gt;"sms"&lt;/span&gt;,
  &lt;span class="s2"&gt;"message_uuid"&lt;/span&gt;: &lt;span class="s2"&gt;"461c9502-3c2f-4af9-8862-c5a8eccf6cfe"&lt;/span&gt;,
  &lt;span class="s2"&gt;"timestamp"&lt;/span&gt;: &lt;span class="s2"&gt;"2024-07-09T18:45:56Z"&lt;/span&gt;,
  &lt;span class="s2"&gt;"usage"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"price"&lt;/span&gt;: &lt;span class="s2"&gt;"0.0057"&lt;/span&gt;,
    &lt;span class="s2"&gt;"currency"&lt;/span&gt;: &lt;span class="s2"&gt;"EUR"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"message_type"&lt;/span&gt;: &lt;span class="s2"&gt;"text"&lt;/span&gt;,
  &lt;span class="s2"&gt;"text"&lt;/span&gt;: &lt;span class="s2"&gt;"Inbound SMS from the Vonage API"&lt;/span&gt;,
  &lt;span class="s2"&gt;"context_status"&lt;/span&gt;: &lt;span class="s2"&gt;"none"&lt;/span&gt;,
  &lt;span class="s2"&gt;"origin"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"network_code"&lt;/span&gt;: &lt;span class="s2"&gt;"23415"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"sms"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"num_messages"&lt;/span&gt;: &lt;span class="s2"&gt;"1"&lt;/span&gt;,
    &lt;span class="s2"&gt;"count_total"&lt;/span&gt;: &lt;span class="s2"&gt;"1"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it!&lt;/p&gt;

&lt;p&gt;You have successfully received a webhook on your local development environment using the Hookdeck CLI. You also inspected the webhook payload and server response, and replayed a webhook using the Hookdeck Console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more about Hookdeck
&lt;/h2&gt;

&lt;p&gt;This guide demonstrated how to use the &lt;a href="https://github.com/hookdeck/hookdeck-cli" rel="noopener noreferrer"&gt;Hookdeck CLI&lt;/a&gt; as a replacement for ngrok for local asynchronous web development. The Hookdeck CLI is free to use, and you don't need a Hookdeck account.&lt;/p&gt;

&lt;p&gt;If you're interested in finding out more about Hookdeck including features such as &lt;a href="https://hookdeck.com/docs/transformations?ref=dev-to-cli" rel="noopener noreferrer"&gt;transformations&lt;/a&gt;, and &lt;a href="https://hookdeck.com/docs/filters?ref=dev-to-cli" rel="noopener noreferrer"&gt;filtering&lt;/a&gt;, benefitting from functionality like &lt;a href="https://hookdeck.com/docs/retries?ref=dev-to-cli" rel="noopener noreferrer"&gt;configurable retries&lt;/a&gt;, and generally using Hookdeck as your reliable inbound webhook infrastructure, head to &lt;a href="https://hookdeck.com?ref=dev-to-cli" rel="noopener noreferrer"&gt;hookdeck.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ngrok</category>
      <category>webhooks</category>
      <category>cli</category>
      <category>eventdriven</category>
    </item>
    <item>
      <title>Add Webhook Verification, Queueing, Filtering, and Retry Logic to Any Vercel Deployed Endpoint</title>
      <dc:creator>Phil Leggetter</dc:creator>
      <pubDate>Mon, 06 May 2024 18:38:18 +0000</pubDate>
      <link>https://forem.com/hookdeck/add-webhook-verification-queueing-filtering-and-retry-logic-to-any-vercel-deployed-endpoint-4bnp</link>
      <guid>https://forem.com/hookdeck/add-webhook-verification-queueing-filtering-and-retry-logic-to-any-vercel-deployed-endpoint-4bnp</guid>
      <description>&lt;p&gt;Today we're excited to announce the Hookdeck Vercel Middleware. Vercel middleware that convert any Vercel application endpoint into an asynchronous endpoint with authentication, filters, queueing, throttling, and retry logic.&lt;/p&gt;

&lt;p&gt;Every month, we deliver hundreds of thousands of asynchronous events to Vercel application endpoints, so we want to make debugging and building with webhooks and other asynchronous messaging use cases on Vercel even easier.&lt;/p&gt;

&lt;p&gt;The Hookdeck Vercel Middleware requires minimal effort (install + simple configuration) and a DX that fits into a code-first workflow. So, within a few minutes, you've converted a Next.js route, for example, into a reliable and feature-rich asynchronous endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm13fklf1jqk6n6wirsx.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm13fklf1jqk6n6wirsx.jpeg" alt="Hookdeck Vercel Middleware configuration" width="800" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Along with the simple setup, I believe this solves a real problem with numerous use cases. The main one is handling webhooks (Stripe, Shopify, Twilio, etc.) at a scale where you really don't want to miss a webhook.&lt;/p&gt;

&lt;p&gt;Here's a 5-minute video of our CEO and co-founder, Alex, installing, configuring, and deploying a Next.js app: &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/bQzyhyVNbY8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can see the quickstart instructions (along with the middleware source) in the &lt;a href="https://github.com/hookdeck/hookdeck-vercel" rel="noopener noreferrer"&gt;Hookdeck Vercel Middleware GitHub repo&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://hookdeck-vercel-example.vercel.app/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2w9nnrnnm0j9d0p5ry6k.png" alt="Hookdeck Vercel demo app" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's also &lt;a href="https://github.com/hookdeck/hookdeck-vercel-example" rel="noopener noreferrer"&gt;example Next.js app&lt;/a&gt; with a &lt;a href="https://hookdeck-vercel-example.vercel.app/" rel="noopener noreferrer"&gt;deployed demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let us know what you think.&lt;/p&gt;

</description>
      <category>vercel</category>
      <category>javascript</category>
      <category>webhooks</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Building and measuring a sign up funnel with Supabase, Next.js, and PostHog</title>
      <dc:creator>Phil Leggetter</dc:creator>
      <pubDate>Thu, 21 Oct 2021 17:08:15 +0000</pubDate>
      <link>https://forem.com/posthog/building-and-measuring-a-sign-up-funnel-with-supabase-nextjs-and-posthog-2hh9</link>
      <guid>https://forem.com/posthog/building-and-measuring-a-sign-up-funnel-with-supabase-nextjs-and-posthog-2hh9</guid>
      <description>&lt;p&gt;With the number of software frameworks and services available to help with developer productivity and feature building, it's never been a better time to be a software developer. One of most important things you'll have to build, whether building a SaaS, developer tool, or consumer app, is a sign up flow that begins on a landing page and ideally results in a successful sign up. The purpose of this sign up flow is to get as many real users through to being successfully signed up on your app or platform. So, it's important that you can measure whether your sign up flow is converting and where any potential sign ups are dropping out of that funnel.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll create a simple sign up flow in a Next.js app, starting with a Supabase example for authentication. We'll then look at how you can instrument that sign up flow using the &lt;a href="https://posthog.comdocs/integrate/client/js"&gt;PostHog JavaScript SDK&lt;/a&gt; and create a sign up &lt;a href="https://posthog.comdocs/user-guides/funnels"&gt;funnel visualization&lt;/a&gt; within PostHog to analyze the success - or failure - of the sign up flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you begin
&lt;/h2&gt;

&lt;p&gt;The application in this tutorial is built entirely upon open-source technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; is feature rich, Node.js open-source React framework for building modern web apps.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://supabase.io/"&gt;Supabase&lt;/a&gt; is an open-source alternative to Firebase offering functionality such as a Postgres database, authentication, realtime subscriptions and storage.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://posthog.com"&gt;PostHog&lt;/a&gt; is an open-source product analytics platform with features including feature flags, session recording, analysis of trends, funnels, user paths, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To follow this tutorial along, you need to have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;a href="https://posthog.com/signup/self-host"&gt;self-hosted instance of PostHog&lt;/a&gt; or &lt;a href="https://app.posthog.com/signup"&gt;sign up for PostHog Cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://supabase.io/docs/guides/hosting/overview"&gt;self-hosted instance of Supabase&lt;/a&gt; or &lt;a href="https://app.supabase.io/api/login"&gt;sign up for a hosted Supabase account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/download/"&gt;Node.js&lt;/a&gt; installed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's easier to get up an running with the cloud hosted options. If you want to go with self-hosted then the &lt;a href="https://posthog.comdocs/self-host/deploy/digital-ocean"&gt;DigitalOcean PostHog 1-click deployment&lt;/a&gt; makes getting started with PostHog much easier. For Supabase, &lt;a href="https://supabase.io/docs/guides/hosting/docker"&gt;the Docker setup&lt;/a&gt; appears to be the best option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap sign up with Supabase Auth
&lt;/h2&gt;

&lt;p&gt;Rather than building sign up from scratch, let's instead start with an existing Supabase-powered example.&lt;/p&gt;

&lt;p&gt;Run the following in your terminal to bootstrap a Next.js application with pre-built sign up and login functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app &lt;span class="nt"&gt;--example&lt;/span&gt; https://github.com/PostHog/posthog-js-examples/tree/bootstrap/supabase-signup-funnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will look similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx create-next-app --example https://github.com/PostHog/posthog-js-examples/tree/bootstrap/supabase-signup-funnel
✔ What is your project named? … nextjs-supabase-signup-funnel
Creating a new Next.js app in /Users/leggetter/posthog/git/nextjs-supabase-signup-funnel.

Downloading files from repo https://github.com/PostHog/posthog-js-examples/tree/bootstrap/supabase-signup-funnel. This might take a moment.

Installing packages. This might take a couple of minutes.

Initialized a git repository.

Success! Created nextjs-supabase-signup-funnel at /Users/leggetter/posthog/git/nextjs-supabase-signup-funnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted for a name for your app and the files will be downloaded into a directory with that name. The directory structure of your app will look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
├── components
│   └── Auth.js
├── lib
│   └── UserContext.js
├── package.json
├── pages
│   ├── _app.js
│   ├── api
│   │   ├── auth.js
│   │   └── getUser.js
│   ├── auth.js
│   └── profile.js
├── .env.local.example
├── style.css
└── utils
    └── initSupabase.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;components/Auth.js&lt;/code&gt; is the sign up, login, magic link, and forgot password component that makes use of Supabase Auth.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/UserContext.js&lt;/code&gt; provides functionality to get the current user from within a component wrapped in a &lt;code&gt;&amp;lt;UserContext /&amp;gt;&lt;/code&gt;, if a user is logged in.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/_app.js&lt;/code&gt; a Next.js &lt;a href="https://nextjs.org/docs/advanced-features/custom-app"&gt;custom app component&lt;/a&gt; used to initialize all pages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/api/*&lt;/code&gt; serverless API endpoints used within the Supabase authentication.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/auth.js&lt;/code&gt; is the authentication page that uses the &lt;code&gt;Auth&lt;/code&gt; component.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/profile.js&lt;/code&gt; is a page used to demonstrate server-side rendering.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.local.example&lt;/code&gt; environment variables/configuration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;styles.css&lt;/code&gt; basic styling.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utils/initSupabase.js&lt;/code&gt; initializes a &lt;a href="https://supabase.io/docs/reference/javascript/supabase-client"&gt;Supabase client&lt;/a&gt; used to interact with Supabase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we understand the basic structure of the bootstrapped application, let's get it up and running.&lt;/p&gt;

&lt;p&gt;The one last piece of setup that's required before running the app is to create a Supabase project, set some auth config, and add the credentials from that to a &lt;code&gt;.env.local&lt;/code&gt;. To create the &lt;code&gt;.env.local&lt;/code&gt; run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.local.example .env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, head to the &lt;a href="https://app.supabase.io/"&gt;Supabase dashboard&lt;/a&gt; to create a project. Click the &lt;strong&gt;New project&lt;/strong&gt; button and you'll be presented with a "Create new project" dialog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej3f0sziococid8iulj6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej3f0sziococid8iulj6.png" alt="Supabase Create new project dialog" width="685" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may need to select an Organization. You will need to enter details for a project name, database password, and choose a deployment region. Once done, click the &lt;strong&gt;Create new project&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;You'll then be presented with a page showing &lt;strong&gt;Project API keys&lt;/strong&gt; and &lt;strong&gt;Project Configuration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1g8t7fkovwiam0rrv37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1g8t7fkovwiam0rrv37.png" alt="Supabase Project API keys and Project Configuration" width="733" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Update the contents of &lt;code&gt;.env.local&lt;/code&gt; as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update the &lt;code&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/code&gt; value to be the URL from &lt;strong&gt;Project Configuration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/code&gt; value to be the API key tagged with &lt;code&gt;anon&lt;/code&gt; and &lt;code&gt;public&lt;/code&gt; from &lt;strong&gt;Project API keys&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, within the Supabase dashboard project settings select &lt;strong&gt;Auth settings&lt;/strong&gt; and add &lt;code&gt;http://localhost:3000/auth&lt;/code&gt; to the &lt;strong&gt;Additional Redirect URLs&lt;/strong&gt; field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiurxtsq1s8sen2fl9ljh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiurxtsq1s8sen2fl9ljh.png" alt="Supabase Auth settings" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the Supabase configuration in place, we can run the app with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then navigate to &lt;code&gt;http://localhost:3000/auth&lt;/code&gt; to try out the Supabase authentication functionality including sign up, login, login/sign up with magic link (email), and forgot password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0quiorsjnzr7n9l1rje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0quiorsjnzr7n9l1rje.png" alt="Supabase basic authentication app UI" width="800" height="949"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you're signed up and logged in the UI will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fro8mqjchmfi4oxyqk31n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fro8mqjchmfi4oxyqk31n.png" alt="Supabase Auth signed in example" width="800" height="949"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll focus on sign up for our application, so try out the sign up with email and password functionality as well as the magic link sign up (note magic link emails for a single email address can be sent once per 60 seconds).&lt;/p&gt;

&lt;p&gt;Once you're familiar with Supabase Auth functionality, we're ready to start to build a simple traditional sign up funnel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a sign up funnel
&lt;/h2&gt;

&lt;p&gt;The goal of this tutorial is to demonstrate how to instrument and measure a sign up flow. So, let's create a very simple sign up flow as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User lands on the main website landing page which has two CTAs (call-to-actions) of &lt;strong&gt;SignUp&lt;/strong&gt;. One in the header and one in the landing page hero.&lt;/li&gt;
&lt;li&gt;User clicks on one of the sign up buttons and is taken to the sign up page.&lt;/li&gt;
&lt;li&gt;User enters their details to sign up and submits the form.&lt;/li&gt;
&lt;li&gt;User receives registration verification email.&lt;/li&gt;
&lt;li&gt;User clicks the link in the email and successfully signs up.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Signup flow landing page
&lt;/h3&gt;

&lt;p&gt;We'll keep the landing page really simple. Create a new file, &lt;code&gt;pages/index.js&lt;/code&gt;, with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;curlPostCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
curl -d '{"key1":"value1", "key2":"value2"\}' &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;
     -H "Content-Type: application/json" &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;
     -X POST https://api.awesomeapi.dev/data
`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;curlGetCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
curl -d https://api.awesomeapi.dev/data/{id}
`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;520px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;96px auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;14px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;nav&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Awesome&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="nx"&gt;SignUp&lt;/span&gt;
                            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/nav&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Awesome&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Instantly&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="nx"&gt;awesome&lt;/span&gt; &lt;span class="nx"&gt;functionality&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nx"&gt;SignUp&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/code&amp;gt; something Awesome&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;curlPostCmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/code&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/pre&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/code&amp;gt; something Awesome&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;curlGetCmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/code&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/pre&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;©️&lt;/span&gt;&lt;span class="nx"&gt;Awesome&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/footer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt; &lt;/span&gt;&lt;span class="err"&gt; 
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Index&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As planned, the page has two CTA &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements that send the user to the &lt;code&gt;/auth&lt;/code&gt; page for sign up. One button is in the header and one is in what you could class as a "hero" location.&lt;/p&gt;

&lt;p&gt;This will result in an "Awesome API" landing page that looks as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx010i2njcdg7zlyfu7ne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx010i2njcdg7zlyfu7ne.png" alt="Awesome API example app landing page" width="800" height="949"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to rebrand!&lt;/p&gt;

&lt;p&gt;Now that a landing page is in place we have all the assets required for a basic sign up flow that we want the user to successfully navigate through.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xna3dmswd7es9e2q9p5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xna3dmswd7es9e2q9p5.gif" alt="Supabase Next.js sign up flow animation" width="494" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate with PostHog
&lt;/h2&gt;

&lt;p&gt;A user can now sign up with our app but there are a number of potential drop-off points within the funnel. So, let's integrate the PostHog JavaScript SDK to instrument the user sign up journey.&lt;/p&gt;

&lt;p&gt;Add two new environment variables to &lt;code&gt;.env.local&lt;/code&gt; that will be used with the PostHog JavaScript SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_POSTHOG_API_KEY=your_posthog_api_key
NEXT_PUBLIC_POSTHOG_HOST=your_posthog_host
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value for &lt;code&gt;NEXT_PUBLIC_POSTHOG_API_KEY&lt;/code&gt; can be found via &lt;strong&gt;Project&lt;/strong&gt; in the left-hand menu of your PostHog app, underneath the &lt;strong&gt;Project API Key&lt;/strong&gt; heading.&lt;/p&gt;

&lt;p&gt;The value for &lt;code&gt;NEXT_PUBLIC_POSTHOG_HOST&lt;/code&gt; is the public URL for your running PostHog instance. If you're using cloud, this is &lt;code&gt;https://app.posthog.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With the required config in place we can install the PostHog JavaScript SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-S&lt;/span&gt; posthog-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new file, &lt;code&gt;utils/initPostHog.js&lt;/code&gt;, and within it add code to initialize the PostHog JavaScript client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;posthog&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posthog-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initPostHog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_POSTHOG_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;api_host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_POSTHOG_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;posthog&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file exports a single function, &lt;code&gt;initPostHog&lt;/code&gt;, that checks to ensure that the current runtime is the browser and, if so, initializes the PostHog JavaScript client with the config we've just stored. It also returns the &lt;code&gt;posthog&lt;/code&gt; client instance so we can use within our app.&lt;/p&gt;

&lt;p&gt;PostHog JS has an &lt;a href="https://posthog.comdocs/integrate/client/js#autocapture"&gt;autocapture&lt;/a&gt; feature that automatically captures browser events (this can be disabled). However, it won't capture navigation events in Next.js where the window doesn't reload, so we need to add some custom code to capture navigations.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;pages/_app.js&lt;/code&gt; and add this code within the &lt;code&gt;MyApp&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initPostHog&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/initPostHog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Init for auto capturing&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posthog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initPostHog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$pageview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we import the &lt;a href="https://reactjs.org/docs/hooks-effect.html"&gt;React &lt;code&gt;useEffect&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://nextjs.org/docs/api-reference/next/router"&gt;Next.js Router&lt;/a&gt; hooks. Within the &lt;code&gt;useEffect&lt;/code&gt; hook we initialize the PostHog JS client using the function we've just created and bind to a &lt;code&gt;routeChangeComplete&lt;/code&gt; on the Next.js router, handling the event within the &lt;code&gt;handleRouteChange&lt;/code&gt; function. When this function is called, we manually trigger a &lt;code&gt;$pageview&lt;/code&gt; event using PostHog JS with &lt;code&gt;posthog.capture('$pageview')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, restart your application so it picks up the new config in &lt;code&gt;.env.local&lt;/code&gt; and head over to the &lt;strong&gt;Events&lt;/strong&gt; section within your PostHog instance and you'll see new events appear as you test out the sign up flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fperctshn38slk4xume4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fperctshn38slk4xume4w.png" alt="Signup funnel events in PostHog UI" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how some of the events can tie in to the flow we're trying to build:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Url / Screen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. User lands on the main website landing page&lt;/td&gt;
&lt;td&gt;Pageview&lt;/td&gt;
&lt;td&gt;locahost:3000/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. User clicks on one of the sign up buttons&lt;/td&gt;
&lt;td&gt;clicked button with text "SignUp"&lt;/td&gt;
&lt;td&gt;locahost:3000/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. User enters their details to sign up and submits the form&lt;/td&gt;
&lt;td&gt;submitted form&lt;/td&gt;
&lt;td&gt;localhost:3000/auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. User receives registration verification email&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no event&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;outside of app&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. User clicks the link in the email and successfully signs up&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no event&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;localhost:3000/auth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From the above table, you can see that we can track everything up until the sign up form submission.&lt;/p&gt;

&lt;p&gt;It is theoretically possible to track the step 4, email verification, if the email provider exposes an email sent notification mechanism via something like as a webhook. So, if Supabase offered a webhook when auth emails were sent we could track this from the server.&lt;/p&gt;

&lt;p&gt;However, we need and should be able to track step 5, when the user has successfully signed up. We know that the user lands on &lt;code&gt;/auth&lt;/code&gt; when they are logged in. If we look at the code for that page there is a &lt;code&gt;user&lt;/code&gt; variable that is set if the user is logged in. So, let's update &lt;code&gt;/pages/auth.js&lt;/code&gt; so we can track a logged in user. First, include the &lt;code&gt;initPostHog&lt;/code&gt; utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initPostHog&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/initPostHog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, update the &lt;code&gt;Index&lt;/code&gt; definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSWR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/getUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;authView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setAuthView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sign_up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posthog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initPostHog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;posthog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loggedIn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code we utilize the &lt;code&gt;initPostHog&lt;/code&gt; function again to reference an initialized PostHog JS instance. We then make two function calls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;posthog.identify(user.email, user)&lt;/code&gt; - since the user is logged in we can identify them. We pass in &lt;code&gt;user.email&lt;/code&gt;, their email address, as a distinct identifier. We also pass in the Supabase &lt;code&gt;user&lt;/code&gt; variable so PostHog has access to additional user data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posthog.capture('loggedIn')&lt;/code&gt; - this triggers a simple &lt;code&gt;loggedIn&lt;/code&gt; event that we can use to identify the user as successfully having logged in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you now go through the login flow, you can map all the required events in PostHog to the sign up funnel that we're building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8ygxig6r0cg7su3uyec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8ygxig6r0cg7su3uyec.png" alt="Mapping events in PostHog to the sign up funnel" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll also see the point at which &lt;code&gt;posthog.identify&lt;/code&gt; is called since the &lt;strong&gt;Person&lt;/strong&gt; associated with the event is now listed with each event entry.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: &lt;strong&gt;posthog.identify&lt;/strong&gt; is being called twice as the &lt;code&gt;Index&lt;/code&gt; function is likely being called twice during the React component life cycle as the values of state variables change.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a sign up funnel in PostHog
&lt;/h2&gt;

&lt;p&gt;Now that we have all the events for our sign up flow we can define a funnel to analyze the user journey and identify drop-off points.&lt;/p&gt;

&lt;p&gt;First, let's recap the events in the funnel and include the new &lt;code&gt;loggedIn&lt;/code&gt; event:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Url / Screen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. User lands on the main website landing page&lt;/td&gt;
&lt;td&gt;Pageview&lt;/td&gt;
&lt;td&gt;locahost:3000/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. User clicks on one of the sign up buttons&lt;/td&gt;
&lt;td&gt;clicked button with text "SignUp"&lt;/td&gt;
&lt;td&gt;locahost:3000/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. User enters their details to sign up and submits the form&lt;/td&gt;
&lt;td&gt;submitted form&lt;/td&gt;
&lt;td&gt;localhost:3000/auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. User receives registration verification email&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no event&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;outside of app&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. User clicks the link in the email and successfully signs up&lt;/td&gt;
&lt;td&gt;loggedIn&lt;/td&gt;
&lt;td&gt;localhost:3000/auth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To begin defining a funnel click on the &lt;strong&gt;New Insight&lt;/strong&gt; left-hand menu item within PostHog and select the &lt;strong&gt;Funnels&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F082qb3k3mvoozu1b6s94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F082qb3k3mvoozu1b6s94.png" alt="New Funnels Insight view" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the left-hand side of the view there is a panel with &lt;strong&gt;Graph Type&lt;/strong&gt; and &lt;strong&gt;Steps&lt;/strong&gt; headings. The &lt;strong&gt;Graph Type&lt;/strong&gt; value is set to &lt;strong&gt;Conversion steps&lt;/strong&gt;, which is what we want. The first of the &lt;strong&gt;Steps&lt;/strong&gt; is set to &lt;strong&gt;Pageview&lt;/strong&gt;. As we flesh out the steps, the funnel visualization will appear on the right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 - User lands on landing page
&lt;/h3&gt;

&lt;p&gt;The first step within the funnel is the user landing on the main website landing page with a path of &lt;code&gt;/&lt;/code&gt;. So, the event is correctly set to &lt;strong&gt;Pageview&lt;/strong&gt; but we need to filter the event by path. To do this, click the filter icon next to the step and filter on &lt;strong&gt;Path Name&lt;/strong&gt; where the path value is &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz5vtd3yze7n0cabnv93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz5vtd3yze7n0cabnv93.png" alt="Step 1 - filter on landing page based on path name" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A funnel visualization won't appear at this point because a funnel must have more than one step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - User clicks SignUp button
&lt;/h3&gt;

&lt;p&gt;To add the second step, click either of the &lt;strong&gt;Add funnel step&lt;/strong&gt; buttons. Change the event to &lt;strong&gt;Autocapture&lt;/strong&gt; since the event we're looking to make use of was one &lt;a href="https://posthog.comdocs/integrate/client/js#autocapture"&gt;autocaptured&lt;/a&gt; by the PostHog JS SDK. Then, set a filter. When you click the filter icon this time, select the &lt;strong&gt;Elements&lt;/strong&gt; tab and select the &lt;strong&gt;Text&lt;/strong&gt; property. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlcqpkkk21seqe6daukt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlcqpkkk21seqe6daukt.png" alt="Step 2 - user clicks sign up button filter on element text" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the filter value choose &lt;code&gt;SignUp&lt;/code&gt;, which should be pre-populated based on the values that PostHog has already ingested from our testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ab05do8p5y0log2vj6x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ab05do8p5y0log2vj6x.png" alt="Step 2 - user clicks sign up button filter on element text value" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you populate this step, you'll see the funnel visualization appear.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: you could also have done a &lt;strong&gt;Pageview&lt;/strong&gt; again here, filtered by a &lt;strong&gt;Path Name&lt;/strong&gt; value of &lt;code&gt;/auth&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - User submits sign up form
&lt;/h3&gt;

&lt;p&gt;For this step we want to track the sign up form submission. So, create a new step with an event of &lt;strong&gt;Autocapture&lt;/strong&gt; and a first filter on the &lt;strong&gt;Event Type&lt;/strong&gt; property (not be confused with the top-level event) with a value of "submit" for the form submission.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygmq59e9dix8aoo7n1zq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygmq59e9dix8aoo7n1zq.png" alt="Step 3 - filter on Event Type of submit" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, the above filter will track all form submissions. This may include forms other than the sign up form. So, add a second filter specifically identifying the sign up form based. To do this, select the &lt;strong&gt;Elements&lt;/strong&gt; tab, choose &lt;strong&gt;CSS Selector&lt;/strong&gt;, and set the selector value as &lt;code&gt;[id="sign_up_form"]&lt;/code&gt; to identify the &lt;code&gt;id&lt;/code&gt; attribute as having a value of &lt;code&gt;sign_up_form&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12klv58n2m0otpu6lp45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12klv58n2m0otpu6lp45.png" alt="Step 3 - CSS Selector filter" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - User receives registration email
&lt;/h3&gt;

&lt;p&gt;As noted in the table above, we don't presently have a way of tracking this because it happens on systems outside of our control. Remember, though, it may be that an email provider could integrate with PostHog to also track email events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F660dks8u2k2z7niy4wqa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F660dks8u2k2z7niy4wqa.png" alt="Step 4 - Supabase sign up verification email" width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - User clicks on link in email and logs into app
&lt;/h3&gt;

&lt;p&gt;This represents successful completion of our sign up funnel. We added some custom code for this step earlier where a &lt;code&gt;loggedIn&lt;/code&gt; event was captured. Of course, for a user to have successfully logged in it does also mean that the sign up has been successful.&lt;/p&gt;

&lt;p&gt;So, add a new step to the funnel and select the &lt;code&gt;loggedIn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lmjin69k6yey2xynz4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lmjin69k6yey2xynz4u.png" alt="Step 5 - user signed up and logged in" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The funnel is now complete and we can see the journey of users through the sign up funnel, users that have dropped off, and users that have completed sign up.&lt;/p&gt;

&lt;p&gt;You can adjust the options in the right-hand panel, if required. For example, you can change the orientation of the funnel visualization from left-to-right to top-to-bottom, the computation in the steps from &lt;strong&gt;Overall converstion&lt;/strong&gt; to &lt;strong&gt;Relative to previous step&lt;/strong&gt;, and the period of time the Funnel is calculated over.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8by4hmu2w1euwekbej3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8by4hmu2w1euwekbej3d.png" alt="Funnel visualization options" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, you can save the Funnel, giving it a name of &lt;strong&gt;Signup Funnel&lt;/strong&gt;, and add it to a Dashboard by clicking &lt;strong&gt;Save &amp;amp; add to dashboard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3dco1swgze2to03gsqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3dco1swgze2to03gsqv.png" alt="Save and name sign up funnel" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you've learned how to create a sign up flow with Next.js and Supabase Auth. You've then ensured all the necessary application events are being ingested into PostHog. This then allows you to define the sign up flow as a sign up Funnel so you can measure the success of the user journey and identify where users drop off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where next?
&lt;/h2&gt;

&lt;p&gt;Here are a few examples of where you could explore next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Actions instead of Events
&lt;/h3&gt;

&lt;p&gt;We've made extensive use of &lt;a href="https://posthog.comdocs/user-guides/events"&gt;Events&lt;/a&gt; within this tutorial. However, it can be beneficial to wrap events up into something called &lt;a href="https://posthog.comdocs/user-guides/actions"&gt;Actions&lt;/a&gt;. Actions let you group multiple events which can then be used within Insights, such as Funnels.&lt;/p&gt;

&lt;p&gt;For example, in this tutorial we used an &lt;strong&gt;Event Type&lt;/strong&gt; and a &lt;strong&gt;CSS Selector&lt;/strong&gt; to track the sign up form submission. If we were to instead create an Action and call it &lt;strong&gt;Sign up form submitted&lt;/strong&gt; this Action could be used within the Sign up Funnel and also easily reused within other Insights. So, why not take a look at creating some reusable Actions, update the existing Funnel to use them, and try creating some other Insights?&lt;/p&gt;

&lt;h3&gt;
  
  
  Track email sending
&lt;/h3&gt;

&lt;p&gt;We were unable to track the email sending within this tutorial. How about exploring a way to add capture a &lt;code&gt;signUpEmailSent&lt;/code&gt; event within PostHog when the verification email is sent?&lt;/p&gt;

&lt;p&gt;There are a couple of options here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Supabase uses a tool called &lt;a href="https://github.com/supabase/gotrue"&gt;GoTrue&lt;/a&gt; which does support webhook configuration for email events such as &lt;code&gt;validate&lt;/code&gt;, &lt;code&gt;signup&lt;/code&gt; or &lt;code&gt;login&lt;/code&gt;. Why not get involved in the Supabase community and see if these events can be exposed via Supabase?&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Enable Custom SMTP&lt;/strong&gt; within Supabase and use a third-party email provider that exposes webhooks for email events?&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>nextjs</category>
      <category>supabase</category>
      <category>javascript</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Announcing the tru.ID CLI</title>
      <dc:creator>Phil Leggetter</dc:creator>
      <pubDate>Wed, 03 Mar 2021 01:55:09 +0000</pubDate>
      <link>https://forem.com/tru-id/announcing-the-tru-id-cli-18cn</link>
      <guid>https://forem.com/tru-id/announcing-the-tru-id-cli-18cn</guid>
      <description>&lt;p&gt;As a developer-first and API-first company, we prioritise tools that will make you, the developer, successful. Today we're excited to announce the tru.ID CLI: built on top of our public APIs, open sourced, and your go-to developer tool when building on the &lt;strong&gt;tru.ID&lt;/strong&gt; platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a CLI?
&lt;/h2&gt;

&lt;p&gt;Many of us spend a lot of time in the terminal, so a CLI enables you to work from where you already are. IDEs such as Visual Studio Code and Android Studio come with in-built terminals. But having a CLI also allows us to build integrations into existing development workflows, via scripts, outside of the IDE. For example, &lt;a href="https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CustomizingYourWorkflow.html"&gt;Xcode behaviours&lt;/a&gt; or integration into CI/CD workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Oclif &amp;amp; Open Source
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="http://tru.ID"&gt;tru.ID&lt;/a&gt; CLI is built upon the excellent &lt;a href="https://oclif.io/"&gt;oclif open CLI framework&lt;/a&gt; by Heroku. Oclif provides a foundation for CLI functionality such as subcommands, command arguments and flags, plugin support and lots of utilities that enable us to focus on features instead of building our own CLI framework.&lt;/p&gt;

&lt;p&gt;If you'd like to see how we've used Oclif the &lt;a href="https://github.com/tru-ID/tru-cli"&gt;&lt;strong&gt;tru.ID&lt;/strong&gt; CLI&lt;/a&gt; is on GitHub. Within the GitHub repo you'll find two branches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;main&lt;/code&gt; for the latest stable release&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;canary&lt;/code&gt; if you'd like to try out the latest features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'd love to hear what you think so please ask questions, share ideas and raise PRs on the &lt;a href="https://github.com/tru-ID/tru-cli/issues"&gt;tru-ID/cli repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and Setup the &lt;strong&gt;tru.ID&lt;/strong&gt; CLI
&lt;/h2&gt;

&lt;p&gt;For the moment the only installation option is via NPM so you'll need Node.js installed.&lt;/p&gt;

&lt;p&gt;With Node.js installed you install the &lt;strong&gt;tru.ID&lt;/strong&gt; CLI as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @tru_id/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you prefer &lt;a href="https://yarnpkg.com/"&gt;Yarn&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn global add @tru_id/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To try out the latest features use &lt;code&gt;@tru_id/cli@canary&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;tru&lt;/code&gt; command installed, setup the CLI with the credentials you'll find within the &lt;a href="https://tru.id/console"&gt;&lt;strong&gt;tru.ID&lt;/strong&gt; console&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru setup:credentials &lt;span class="o"&gt;{&lt;/span&gt;client_id&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;client_secret&lt;span class="o"&gt;}&lt;/span&gt; eu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;eu&lt;/code&gt; is the region where your account data is stored and, at the time of this blog post, is the only region we have available (more coming soon).&lt;/p&gt;

&lt;p&gt;You can now query the credit you have available using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru workspaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the output will be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;credentials.client_id                data_residency balance.amount_available balance.currency created_at
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx EU             28                       API              2020-08-12T12:46:56+0000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a &lt;strong&gt;tru.ID&lt;/strong&gt; Project
&lt;/h2&gt;

&lt;p&gt;Projects are configuration containers within the &lt;strong&gt;tru.ID&lt;/strong&gt; platform and you'll normally have a one-to-one mapping of a tru.ID project to an application that you are developing (though you may also create a project per environment: &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt;). A key piece of configuration are the project &lt;code&gt;credentials&lt;/code&gt; which contain a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; that you use to authenticate interactions with the tru.ID APIs.&lt;/p&gt;

&lt;p&gt;You create projects using the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru projects:create &lt;span class="s2"&gt;"My Awesome Project"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you'll see output such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Creating Project &lt;span class="s2"&gt;"My Awesome Project"&lt;/span&gt;
Project configuration saved to &lt;span class="s2"&gt;"~/my_awesome_project/tru.json"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tru.json&lt;/code&gt; file contains your project configuration including the project credentials. Don't check this into source control!&lt;/p&gt;

&lt;p&gt;With a project created you can &lt;code&gt;cd my_awesome_project&lt;/code&gt; so the CLI can read the &lt;code&gt;tru.json&lt;/code&gt; file from the &lt;code&gt;cwd&lt;/code&gt; and you have everything you need to fully utilise the &lt;strong&gt;tru.ID&lt;/strong&gt; CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying the tru.ID APIs
&lt;/h2&gt;

&lt;p&gt;CLIs normally focus on platform management functionality, such as creating and managing projects, but we decided to also add commands to let you try out our products from the terminal. Of course, you can also try these out programmatically too.&lt;/p&gt;

&lt;h3&gt;
  
  
  SIMCheck
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tru.id/docs/sim-check"&gt;SIMCheck&lt;/a&gt; is an API that queries when the SIM card associated with a phone number last changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru simchecks:create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the output being similar to the following, which prompts for the phone number to be checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;? Please enter the phone number you would like to SIMCheck +&lt;span class="o"&gt;{&lt;/span&gt;phone_number&lt;span class="o"&gt;}&lt;/span&gt;
Creating SIMCheck &lt;span class="k"&gt;for&lt;/span&gt; +&lt;span class="o"&gt;{&lt;/span&gt;phone_number&lt;span class="o"&gt;}&lt;/span&gt;

    status: COMPLETED
    no_sim_change: &lt;span class="nb"&gt;true
    &lt;/span&gt;last_sim_change_at: 2017-01-26T07:16:57+0000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;no_sim_change&lt;/code&gt; value indicates that the SIM card hasn't changed within the last seven (7) days, and &lt;code&gt;last_sim_change_at&lt;/code&gt; specifies the last change date (when I changed my mobile phone provider from giffgaff UK to EE UK).&lt;/p&gt;

&lt;h3&gt;
  
  
  PhoneCheck
&lt;/h3&gt;

&lt;p&gt;PhoneCheck verifies that a phone number is associated with a SIM card within the mobile device. This is achieved via tru.ID's integration with MNOs (Mobile Network Operators) around the world. For a more in-depth look at how this works, check out the &lt;a href="https://tru.id/docs/phone-check/integration"&gt;PhoneCheck integration guide&lt;/a&gt;. Of course, you can also try this out via our CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru phonechecks:create &lt;span class="nt"&gt;--workflow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of this command prompts for a phone number and also displays a QR code to scan on a mobile device. It also provides instructions to disable WiFi on the device, as part of the PhoneCheck workflow is to make a web request over the device's mobile data connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;? Please enter the phone number you would like to PhoneCheck +&lt;span class="o"&gt;{&lt;/span&gt;phone_number&lt;span class="o"&gt;}&lt;/span&gt;
Creating PhoneCheck &lt;span class="k"&gt;for&lt;/span&gt; +&lt;span class="o"&gt;{&lt;/span&gt;phone_number&lt;span class="o"&gt;}&lt;/span&gt;
PhoneCheck ACCEPTED
check_id: 76a36abb-8495-403a-9f71-531c80c6a26b
check_url: https://eu.api.tru.id/phone_check/v0.1/checks/76a36abb-8495-403a-9f71-531c80c6a26b/redirect
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█ ▄▄▄▄▄ █▄█  ██ ▄███ ▀▄▄ ▄▀█ ██ ▄  ▀██▀█▀▀  █  ▄ ██ ▄▄▄▄▄ █
█ █   █ █ ▄▀▄ █▄█▄█▀█████ ▄█ ██▄▄▄▄█▀▀█▀ █ ▀█▄▀▄ ██ █   █ █
█ █▄▄▄█ █▄ █▀  ▀▀▀   ▀▀  ▀█ ▄▄▄   ▄█▄▄   ▀ ▀▄▄▄▀▄██ █▄▄▄█ █
█▄▄▄▄▄▄▄█▄▀▄█▄█▄█▄█▄▀▄█▄█ ▀ █▄█ █▄█ ▀▄▀ ▀ ▀ ▀▄█ ▀▄█▄▄▄▄▄▄▄█
█    ▀▀▄▀▄▄▀▄▀▀█▄▄ ▀▄▄  ▀ ▄ ▄▄▄  ███ █▀██ ▀▀ ▀▀▄▄  ██ ▄▄▀▄█
███▀▄▀▄▄█▄ ▀   ▀██ ██▄▄▄▀ ▄▄██▀ █▄ ▀ ▀▀▄ ▄ ▀█ ▄  ▄████ █  █
█▀▄█ ▀▀▄█▀ ▄█▄▄ █▄█▄  █▄▄██ █▀▄▄ ▀ ▄▀▄   ▀▄▄▀▀ ▄▄█▄▀▄▀ █▄▄█
█▄██ ▄ ▄▄ ▄▀▀▀▀█ ▄▀██▄▄▀▀ ▄▄▄█ ▄▄ ██▄█ █▀ █ █ ▄█▀▀ ▄▀▀▀ █▀█
█▄▀▀▄██▄█▄▄▄  ▄▄▄ █▀██▄█ ▄ █▄█ ▄▄▄▄ ▀█▀▄▀▀▄▄▄▄▄  ▄ █ █▄██ █
█ ▀ ▄ ▀▄▄▀  █▄▀▀ ▄  ██▀█▀▄▀ ▄█ ▀██▀▄█▀▄ █▀▀█▀█▀  ▀▀▄▀▀▀█▀▀█
███ ▄▀ ▄ ▀  ▀▀ ██ ▄███  ▀ ▄▀▄ █▀██▄▄▀▀███ ▄ ▀▀ ▄▀ █▄█▄▀ █▀█
█▀▄ ▀██▄▀ █ ▀█ ▄▀▄ █ ▄  ▀███▄▄█▄█▄▄▄█▀▄▀█▀▀ ▄█  █▄▀▀█▄██▄ █
█▀▄██ ▄▄ ▄ █ ▀ ▄█▄   ▀█▀█▄ ▀█▀▀█▄▀ █▄▄ ▀███▄▄█ ██▄  ▀█▄█▄▄█
██▄▀  ▄▄▄ ▀▀ ▀█▀▀ ▄▄▄▀█▄█ ▄ ▄▄▄   █▄▀██▄▀▀█▄▄▀▄▄▀ ▄▄▄ ▀▀▀▀█
█ ▀ ▄ █▄█ ▀█▀ ▄▀▀ █▄██▀▀ █▀ █▄█ ▄▄ ▀█▀ ▄▄▀ ██▄▄▀▄ █▄█ ▀▄▀ █
██▀▀█ ▄▄  ██▄▄█▄ ▄█▀ ▀█ ▀█▄  ▄▄  ▄█▄▄ █  ▀▄▀██ ▄▄  ▄ ▄██ ▀█
█ ▄██ ▄▄ ███▀█▄█▄███ ▄    █▄▀█ █ ▀ ▄▀▀▄██▄▄▀█▀██▀█▀▄ █▀▄▄▀█
██▄   ▀▄█▀▄ ▄▀██▀  ▀ █▄▀ ▄▄█ ▀███ █ █  █▀▄█▄▄ █▄█▀█ ▄ ███▄█
█▄██▀ █▄█▄▄█ ▄█▀███ ▀██▀█▄█▄ ▄ ▄ ▀ █▀ █ ██▄▄█▀▀█▀▀▄▄▀█ ▀▀██
█▀▄█▄▄▄▄▄█  █ ██ ▄▀▄  ▄▀▀ ▀ ▄▄ █ ▀█▀ ▀█ █▄▀ ▀▀██▀ █▀▄█ ▀█▄█
█▀▄█ █▄▄▀ █▀▀ █▀  ▀▀██▄▀█▄▀▄▀▀▄██▄▀▀████ ▀ ██▄ ▀▄█▄▀▄ ▀██▄█
██  ▀▀▄▄▀ ▄ ▄██▄▀▀█▄▀ █ ▄▄▄     ▄█ ▄  ▀   █▄ ▄ ▀██ █ ██▄▄▀█
█▀▄▀▄█▀▄ ▀▀ █ ▄▀ █▀▀ ▄ █▄ ▄  █▀█▀▀ ▄████▀▄▀▀ ▀███▄ ▀██▄ ▄ █
█ ▀ ▀▀▄▄▄ ██▀▄▀   ▀█▀█ ▀▀█  ▄ ███▀▀▄▄▀ █▀▄█ ▄ █  █▀ ██▀▄  █
███████▄█▀▀▄ ▀▄█ ██ ▀ ▀██▄▀ ▄▄▄ ▀▀███▄  ▄█▀▄▀███▀ ▄▄▄ ▀▀ ██
█ ▄▄▄▄▄ ██ █ █▄█▀█  ▀ ███ ▄ █▄█ █▀▄▀█▀ ▄▄ ▄ ▄▀▄█▄ █▄█ █▀▄▀█
█ █   █ █▀ █▀▄ █▀█ ▀ ▄ █▀▄▀  ▄▄   ▀▀█▀██▄▀█▄▄▄█   ▄▄ ▄▀▄ ██
█ █▄▄▄█ █ █▄▄▄▀▀  ▄▄▄▀▄ ▀ ▀▀█▀██ ▄  ▀ █ ██▄██▄▀▄▀ █▀███▄█▀█
█▄▄▄▄▄▄▄█▄▄▄▄▄▄▄█▄▄██▄▄▄█████▄▄███▄▄▄██▄▄▄▄▄██▄▄▄██▄█▄▄▄███

Please ensure the mobile phone with the phone number +&lt;span class="o"&gt;{&lt;/span&gt;phone_number&lt;span class="o"&gt;}&lt;/span&gt; is disconnected from WiFi and is using your mobile data connection.

Then scan the QR code and navigate to the check_url.

Waiting &lt;span class="k"&gt;for &lt;/span&gt;a PhoneCheck result... &lt;span class="k"&gt;done

&lt;/span&gt;PhoneCheck Workflow result:
    status:  COMPLETED
    match:  &lt;span class="nb"&gt;true&lt;/span&gt; ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QR codes in the terminal. Pretty cool, huh!&lt;/p&gt;

&lt;p&gt;As well as creating resources you can also query existing ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru phonechecks:list 76a36abb-8495-403a-9f71-531c80c6a26b
check_id                             created_at               status    match charge_currency charge_amount
76a36abb-8495-403a-9f71-531c80c6a26b 2021-02-23T21:08:56+0000 COMPLETED &lt;span class="nb"&gt;true  &lt;/span&gt;API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also try out &lt;code&gt;tru phonechecks:list&lt;/code&gt; with flags such as &lt;code&gt;--search&lt;/code&gt; to add query conditions the API request, &lt;code&gt;--filter&lt;/code&gt; to filter a result from the API, and &lt;code&gt;--output&lt;/code&gt; to change the output format e.g. &lt;code&gt;--output json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  SubscriberCheck
&lt;/h3&gt;

&lt;p&gt;The CLI also supports &lt;a href="https://tru.id/docs/subscriber-check"&gt;SubscriberCheck&lt;/a&gt;, which runs a PhoneCheck but also includes a SIMCheck within the result. We'll leave trying that out to you, but here's the creation command that runs through the QR code workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru subscriberchecks:create &lt;span class="nt"&gt;--workflow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Else Can the CLI Do?
&lt;/h2&gt;

&lt;p&gt;Two other commands worth highlighting are &lt;code&gt;oauth2&lt;/code&gt; and &lt;code&gt;coverage&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth2
&lt;/h3&gt;

&lt;p&gt;Our APIs use OAuth2 credentials and access tokens for authentication. But we know that sometimes you just want access to an access token without having to concatenate a project &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;, Base 64 encode that value, make a request to the &lt;a href="https://tru.id/docs/reference/api#operation/oauth2-token"&gt;&lt;code&gt;/token&lt;/code&gt; endpoint&lt;/a&gt; to create an access token, and then eventually getting to use it. So, the &lt;code&gt;oauth2&lt;/code&gt; command makes it a bit easier to create an access token when you're just trying things out.&lt;/p&gt;

&lt;p&gt;Note: Running the following command from a directory that contains a &lt;code&gt;tru.json&lt;/code&gt; project configuration file will use the credentials for the project. If you run the command without a &lt;code&gt;tru.json&lt;/code&gt; in the &lt;code&gt;cwd&lt;/code&gt; you'll use the credentials you supplied in the &lt;code&gt;tru setup:credentials&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru oauth2:token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you just need to copy the access token and use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;access_token
GXuXq1DKa2LdBikD0jC8DenJgJTdgPSONLC0TpYqIzQ.ko9hRxKojOYhSvWkUBsPtHNPSUtB5KMwCqw8_FjUW6c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also assign the output to a variable and use it either in a mobile client or to try things out via cURL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;tru oauth2:token &lt;span class="nt"&gt;--no-header&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://eu.api.tru.id/phone_check/v0.1/checks/76a36abb-8495-403a-9f71-531c80c6a26b
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"check_id"&lt;/span&gt;:&lt;span class="s2"&gt;"76a36abb-8495-403a-9f71-531c80c6a26b"&lt;/span&gt;,&lt;span class="s2"&gt;"status"&lt;/span&gt;:&lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;,&lt;span class="s2"&gt;"match"&lt;/span&gt;:true,&lt;span class="s2"&gt;"charge_amount"&lt;/span&gt;:1.00,&lt;span class="s2"&gt;"charge_currency"&lt;/span&gt;:&lt;span class="s2"&gt;"API"&lt;/span&gt;,&lt;span class="s2"&gt;"created_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2021-02-23T21:08:56+0000"&lt;/span&gt;,&lt;span class="s2"&gt;"updated_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2021-02-23T21:09:26+0000"&lt;/span&gt;,&lt;span class="s2"&gt;"_links"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"self"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"href"&lt;/span&gt;:&lt;span class="s2"&gt;"https://eu.api.tru.id/phone_check/v0.1/checks/76a36abb-8495-403a-9f71-531c80c6a26b"&lt;/span&gt;&lt;span class="o"&gt;}}}&lt;/span&gt;%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Coverage
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tru.id/docs/reference/api#tag/coverage"&gt;Coverage&lt;/a&gt; provides two endpoints and is thus a &lt;a href="https://oclif.io/docs/topics"&gt;"topic"&lt;/a&gt; with two commands in the CLI.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;coverage:country {country_code}&lt;/code&gt; allows you to query if the &lt;strong&gt;tru.ID&lt;/strong&gt; platform has coverage for a given country. You can use either a phone number &lt;a href="https://en.wikipedia.org/wiki/List_of_country_calling_codes"&gt;country code prefix&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru coverage:country +44
product_name     network_id network_name currency amount
Phone Check      23410      O2 UK        API      1
Phone Check      23415      Vodafone UK  API      1
Phone Check      23420      3 UK         API      1
Phone Check      23430      EE UK        API      1
Sim Check        23410      O2 UK        API      1
Sim Check        23415      Vodafone UK  API      1
Sim Check        23420      3 UK         API      1
Sim Check        23430      EE UK        API      1
Subscriber Check 23410      O2 UK        API      1
Subscriber Check 23415      Vodafone UK  API      1
Subscriber Check 23420      3 UK         API      1
Subscriber Check 23430      EE UK        API      1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or a &lt;a href="https://www.iban.com/country-codes"&gt;two-letter alpha country code&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru coverage:country CA
product_name network_id network_name currency amount
Phone Check  302220     Telus        API      1
Phone Check  302610     Bell         API      1
Phone Check  302720     Rogers       API      1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;coverage:reach&lt;/code&gt; also performs a &lt;strong&gt;tru.ID&lt;/strong&gt; platform coverage query but based on the IP address (IPv4 or IPv6) of the device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tru coverage:reach 213.205.197.123
network_id network_name country_code supported_products
23430      EE UK        GB           Phone Check,Sim Check,Subscriber Check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try out the CLI and let us know what you think
&lt;/h2&gt;

&lt;p&gt;In this post we've covered a number of the main commands but there's more that the CLI offers. Either install the &lt;strong&gt;tru.ID&lt;/strong&gt; CLI (&lt;code&gt;npm install -g @tru_id/cli&lt;/code&gt;) and &lt;code&gt;tru --help&lt;/code&gt; to find out more, or take a look at the &lt;a href="https://tru.id/docs/reference/cli"&gt;CLI Reference docs&lt;/a&gt; to see what else is possible.&lt;/p&gt;

&lt;p&gt;We believe the &lt;strong&gt;tru.ID&lt;/strong&gt; CLI offers a good first developer experience of our platform. But we'd love to know what you think via &lt;a href="mailto:feedback@tru.id?subject=CLI%20Feedback"&gt;&lt;/a&gt;&lt;a href="mailto:feedback@tru.id"&gt;feedback@tru.id&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you haven't already, &lt;a href="https://tru.id/signup"&gt;signup for a &lt;strong&gt;tru.ID&lt;/strong&gt; account&lt;/a&gt;, &lt;a href="https://twitter.com/_tru_id"&gt;follow @_tru_id on Twitter&lt;/a&gt; and join our mission to reimagine mobile authentication.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>mobile</category>
      <category>security</category>
      <category>2fa</category>
    </item>
  </channel>
</rss>
