cpursley / webhoox

Inbound webhooks the easy way
MIT License
11 stars 1 forks source link

Standard Webhooks Support #4

Open Nezteb opened 7 months ago

Nezteb commented 7 months ago

tl;dr a few companies worked together to create a specification for webhooks: https://www.standardwebhooks.com/

It'd be cool for this project to somehow conform to the new spec. 😄

cpursley commented 7 months ago

Thanks @Nezteb, open to ideas and PRs on how to conform with their specs.

Nezteb commented 7 months ago

I actually started to make a new repo to build something myself, but then I realized I should make an issue here first, as I'd rather not re-invent the wheel. 😄

I had asked in the Elixir Slack #phoenix channel, but since it has limited history I'll copy-paste the relevant bits:

Nezteb I want to build a Mix package that integrates with Phoenix, specifically one that helps people build webhooks that follow a new specification called "standard webhooks" https://www.standardwebhooks.com/ At first I figured it could be a Plug that Phoenix users would add to a pipeline, but reading more through the spec itself I'm not sure if this library should be more integrated into Phoenix than that. 🤔 Reading through https://mainmatter.com/blog/2018/02/14/handling-webhooks-in-phoenix/, most of which seems to still apply I guess a plug and a series of functions that could be used from a controller? 🤔 The catalyst of my question: twitter.com/supabase/status/1735405662958424485 screenshot_2023-12-14_at_23 24 32@2x

Jeremy Brayton I think the closest example to me is the auth generator. You don't need it for a normal scaffold and you can bolt it on later. I believe it started outside and was then "brought in the fold." I don't have any other examples of framework packages but considering it's a lot of sprinkles and clumps on plug, it probably doesn't need the extension points other frameworks have. Neither of those seem to mention the approach I've adopted, take in a webhook and store it to respond with a 200/OK immediately. Then use a background process to do the work. The docs mention storing idempotency keys in Redis for 5 minutes so the event could sit in storage until it's processed + a timeout. That may be outside what you're looking to implement but I'd love a comprehensive set of defaults or guidance if I were to adopt it. I have a feeling if I didn't use this I'd be rolling my own and it gets tedious real quick.

cpursley commented 7 months ago

Thanks for the additional context! I'd love some help on this if you're up for it.

And I don't mind breaking changes as long as they are versioned correctly.

cpursley commented 7 months ago

I think the first thing to do is document the differences between the current version of webhoox and how they differ from standardwebhooks.

cpursley commented 6 months ago

@Nezteb Have you figured out how to create the proper Standard Webhooks signature using Elixir?

expected_signature =  "v1,6QrvW80BV1T1hZESsZOhKSqc3uaKGE2ceF9RRP2Et28="

id = "msg_p5jXN8AQM9LWM0D4loKWxJek"
timestamp = 1674087231
payload = Jason.encode!(%{"event_type" => "ping", "data" => %{"success" => true}})
signature = "#{id}.#{timestamp}.#{payload}" |> IO.inspect()
"msg_p5jXN8AQM9LWM0D4loKWxJek.1674087231.{\"data\":{\"success\":true},\"event_type\":\"ping\"}"

secret = "MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"  
encoded_signature = :crypto.mac(:hmac, :sha256, secret, signature) |> Base.encode64() |> String.trim() |> IO.inspect()
"9SzW0S/o+gbH+/OiAFy1dAxAXeWmKiK/gClIRhFhF7Y="

signature_with_version = "v1,#{encoded_signature}" |> IO.inspect()
"v1,9SzW0S/o+gbH+/OiAFy1dAxAXeWmKiK/gClIRhFhF7Y="

expected_signature == signature_with_version
false

I've tried this but it does not seem to verify over at: https://www.standardwebhooks.com/verify

Any ideas?

cpursley commented 6 months ago

@Nezteb

I've been working on adding Standard Webhooks support for webhoox here (tests here).

I'd be glad to bring the relevant parts over into this repo. And invite you to review before doing so. Thanks!