travisf

travisf

Stripe Payments webhook handler`

I’m working through this tutorial trying to get payments working with Stripe.

I’ve implemented a Stripe webhook handler:

defmodule Postbox.StripeHandler do
  @behaviour Plug

  alias Plug.Conn

  def init(config), do: config

  def call(%{request_path: "/webhook/payments"} = conn, _params) do
    signing_secret = Application.get_env(:stripity_stripe, :webhook_key)
    [stripe_signature] = Plug.Conn.get_req_header(conn, "stripe-signature")

    with {:ok, body, _} = Plug.Conn.read_body(conn),
         {:ok, stripe_event} =
           Stripe.Webhook.construct_event(body, stripe_signature, signing_secret) do
      Plug.Conn.assign(conn, :stripe_event, stripe_event)
    else
      err ->
        conn
        |> Conn.send_resp(:bad_request, err)
        |> Conn.halt()
    end
  end
end

I’ve copied my webhook secret (:webhook_key directly from the signing secret) but I’m still, continuously getting this error from Stripe: ** (MatchError) no match of right hand side value: {:error, "No signatures found matching the expected signature for payload"}.

This is a test account and I’m using Ngrok for the Webhook address.


Initial question aside I’m wondering if I’m just using Stripe.Checkout.Session do I need to even bother with Webhooks? Willl Stripe returning a successful URL be sufficient?

Most Liked

sorentwo

sorentwo

Oban Core Team

I noticed that the post appears to be deleted earlier this week. Here’s a brief, working example of how to verify stripe webhooks in phoenix.

First, parsed JSON will strip some whitespace and alphabetize keys. To calculate the correct HMAC you need to stash the original unparsed request body. A small body_reader module will do it:

# body_reader.ex
defmodule MyApp.BodyReader do
  def read_body(conn, _opts) do
    # You may want to only do this for certain paths
    with {:ok, raw_body, conn} <- Plug.Conn.read_body(conn) do
      {:ok, raw_body, Plug.Conn.put_private(conn, :raw_body, raw_body)}
    end
  end
end

Configure Plug.Parsers to use the new body_reader module:

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Phoenix.json_library(),
    body_reader: {MyApp.BodyReader, :read_body, []}

Then define a private function to verify the signature in your controller:

defmodule MyAppWeb.StripeController do
  use MyAppWeb, :controller

  plug :verify_signature

  # actions go here

  defp verify_signature(conn, _opts) do
    case get_req_header(conn, "stripe-signature") do
      [header] ->
        ["t=" <> time, "v1=" <> sigv | _] = String.split(header, ",")
        signing_secret = Application.fetch_env!(:my_app, :stripe_signing_secret)

        hmac =
          :hmac
          |> :crypto.mac(:sha256, signing_secret, [time, ".", conn.private.raw_body])
          |> Base.encode16(case: :lower)

        if Plug.Crypto.secure_compare(hmac, sigv) do
          conn
        else
          conn
          |> send_resp(400, "Invalid Signature")
          |> halt()
        end

      _ ->
        conn
        |> send_resp(400, "Missing Signature")
        |> halt()
    end
  end
end

That will do it! If you test your webhook controller, which you certainly should, you’ll need to inject a signature as well. The signature below is fake and computed using a test signing key (whsec_test), a fake timestamp, and :raw_body:

  @signature "t=123456789,v1=34e0846d2ae20d2fcde8c391d069223f72d0518eaf511de69f785470938e1505"

  setup %{conn: conn} do
    conn =
      conn
      |> put_req_header("stripe-signature", @signature)
      |> put_private(:raw_body, ~s({"fake":"body"}))

    {:ok, conn: conn}
  end
wojtekmach

wojtekmach

Hex Core Team

This blog post might be helpful: How we verify webhooks - Dashbit Blog.

Lucassifoni

Lucassifoni

You need to access the body before it has been modified by a subsequent Plug.

Here is an example in Plug.Parsers docs that exposes this very use case :slight_smile:

https://hexdocs.pm/plug/Plug.Parsers.html#module-custom-body-reader

Edit : note that you can also do that with a custom Parser that runs before other Plug.Parsers. You can pattern match on the request path if you wish to avoid copying the raw body for other kind of requests.

@behaviour Plug.Parsers
  alias Plug.Conn

  def parse(%{request_path: "/specific_path"} = conn, _type, _subtype, _headers, opts) do
    case Conn.read_body(conn, opts) do
      {:ok, body, conn} ->
        {:ok, %{raw_body: body}, conn}
      {:more, _data, conn} ->
        {:error, :too_large, conn}
    end
  end

  def parse(conn, _type, _subtype, _headers, _opts), do: {:next, conn}

Check for exhaustiveness with the docs, because I cannot verify it today.

After that, the %{raw_body: r} map gets merged into conn.params. You can access it by pattern matching on conn.params in your controller.

def handle_webhook(%{params: %{raw_body: raw_body}} = conn, params) do

There are many ways to get to the desired result, but the goal is the same : preserve the raw request body.

focused

focused

Look at the tutorial for stripity_stripe: https://tolc.io/blog/stripe-with-elixir-and-phoenix

Maybe you don’t need to implement a plug at all because it’s already implemented in the package? Just use the behaviours in your handler @behaviour Stripe.WebhookHandler.

Initial question aside I’m wondering if I’m just using Stripe.Checkout.Session do I need to even bother with Webhooks? Willl Stripe returning a successful URL be sufficient?

Yes, you need to handle the event after the checkout like I did for setup payment methods:

defmodule MyApp.Endpoint do
  plug(Stripe.WebhookPlug,
    at: "/webhook/stripe",
    handler: MyApp.PaymentService.API.Stripe.EventHandler,
    secret: {MyApp.Config, :cfg, [[:stripity_stripe, :signing_secret]]}
  )
...
defmodule MyApp.PaymentService.API.Stripe.EventHandler do
  @behaviour Stripe.WebhookHandler

  @impl Stripe.WebhookHandler
  def handle_event(%Stripe.Event{type: "checkout.session.completed", data: %{object: %Stripe.Checkout.Session{mode: "setup"}}} = event) do
    ...
  end
...

It’s required to handle all checkout session events. Look at the Stripe API and handle your events according to entity statuses and event types.

In my case I needed to handle the “checkout.session.completed” and then update some payment method fields in the DB to make it active in my system.

Session object API

You should look at the events you need Types of events | Stripe API Reference and handle them as you want, e.g. update a Payment schema entity status.

I would not rely on synchronous API responses only and strongly advice to handle events in a webhook.

P.S.: some useful docs and recommendations about using webhooks - Stripe-Ereignisse in Ihrem Webhook-Endpoint empfangen | Stripe-Dokumentation

justincjohnson

justincjohnson

For anyone else who runs into this, the problem for me had to do with this setting in Bandit.

I was able to get around the problem by changing my config to the following.

config :my_app_web, MyAppWeb.Endpoint,
  # ...
  http_1_options: [max_request_line_length: 50_000]

I haven’t thought deeply about the size to use, but this got around the error I was experiencing.

Where Next?

Popular in Questions Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement