<?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>DEV Community: Alexandre Bouchard</title>
    <description>The latest articles on DEV Community by Alexandre Bouchard (@alexbouchardd).</description>
    <link>https://dev.to/alexbouchardd</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%2F366086%2F75bab972-9f3d-4db6-871e-ba0ad8b9a0c1.jpeg</url>
      <title>DEV Community: Alexandre Bouchard</title>
      <link>https://dev.to/alexbouchardd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexbouchardd"/>
    <language>en</language>
    <item>
      <title>Working with Webhooks: Security</title>
      <dc:creator>Alexandre Bouchard</dc:creator>
      <pubDate>Wed, 05 May 2021 15:31:00 +0000</pubDate>
      <link>https://dev.to/hookdeck/working-with-webhooks-security-2a32</link>
      <guid>https://dev.to/hookdeck/working-with-webhooks-security-2a32</guid>
      <description>&lt;p&gt;&lt;em&gt;The series "Working With Webhooks" explore the most important concepts to consider when receiving incoming webhooks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Working with webhooks exposes an HTTP endpoint that can be called from any actor on your server. Without appropriate measures, this could be extremely unsafe. However, there are now well-understood strategies that ensure your webhook endpoints are secured.&lt;/p&gt;

&lt;p&gt;There are 3 main vectors of attacks that you need to watch out and for and protect yourself against.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1) Man-in-the-middle&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A man-in-the-middle attack is a vulnerability where a third party obtains access to your webhook data by capturing and reading the request. It's essential that you only work with HTTPS URLs (using SSL) when working with sensitive data. Some providers such as &lt;a href="https://hookdeck.com/platforms/shopify-webhooks-features-and-best-practices-guide#incoming-webhooks-verification" rel="noopener noreferrer"&gt;Shopify will enforce this restriction&lt;/a&gt;, but many platforms will let you input unencrypted URLs. By using HTTPS, the content of the request is encrypted and can't be read or used by anyone that intercepts the requests.&lt;/p&gt;

&lt;p&gt;While technically not a man-in-the-middle attack, it's also possible to provide a URL that you do not own. The burden is on you to make sure the URL that you provide as your Webhook URL points to your own server that this server is also secure. Some platforms like &lt;a href="https://hookdeck.com/platforms/guide-to-okta-webhooks-features-and-best-practices#one-time-verification-request" rel="noopener noreferrer"&gt;Otka will perform a one-time verification&lt;/a&gt; to validate that you are indeed the owner of the URL and will not send any webhooks until that verification is performed. Otherwise, audit your server logs and configured webhook URLs to ensure they are pointing to the correct address.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2) Forged requests&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A forged request is a request made to your webhook endpoint that acts as if it was sent from the original source (ex: Shopify), but contains forged data. It's critical to ensure that bad actors are not causing side effects in your systems by sending forged requests to your webhook endpoints. There are multiple strategies to verify that the request isn't coming from an impostor, signature verification being the most popular and secure approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Signature verification&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Platforms that sign their webhooks can be verified by comparing a value found in the headers &lt;a href="https://hookdeck.com/platforms/shopify-webhooks-features-and-best-practices-guide#incoming-webhooks-verification" rel="noopener noreferrer"&gt;(ex: Shopify includes an &lt;code&gt;X-Shopify-Hmac-Sha256&lt;/code&gt; header)&lt;/a&gt; with a computed HMAC made up of a secret key and the content of your webhook payload. In generating a Hash-based message authentication code (HMAC), you are required to use your payload content along with the secret key that the platform provides you when you create a webhook subscription. If the header and your computed signature match, then you can be certain the message was originally sent from the expected provider.&lt;/p&gt;

&lt;p&gt;This works because only you and the platform have access to that secret key and can compute an identical hash. By including the body of the payload within the hash, you can also guarantee that it has not been tempered. In event that this secret is leaked, you should replace it as soon as possible.&lt;/p&gt;

&lt;p&gt;Different providers will use different hashing algorithms (SHA256, SHA128, MD5, etc.), and your implementation should use the same algorithm. Many platforms will also provide SDKs that include the verification logic to spare you most of the trouble.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Example of adding webhook verification for Shopify in Javascript&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To get started with adding webhook verification to the server, run the following 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;npm &lt;span class="nb"&gt;install &lt;/span&gt;raw-body
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw-body module we just installed would help us parse our incoming request body in a way that can be used to generate the hash. The default body-parser module that comes with Express could also be used in place of the raw-body package.&lt;/p&gt;

&lt;p&gt;Next, Import the raw-body &amp;amp; crypto packages into the project by adding the following lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRawBody&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="s2"&gt;raw-body&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;crypto&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="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crypto module would be used to generate the hash from our request body and the secret key provided to us by Shopify.&lt;/p&gt;

&lt;p&gt;Replace the app.post request handler created above with this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&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;/webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="c1"&gt;//Extract X-Shopify-Hmac-Sha256 Header from the requestconst hmacHeader = req.get("X-Shopify-Hmac-Sha256");&lt;/span&gt;

&lt;span class="c1"&gt;//Parse the request Bodyconst body = await getRawBody(req);&lt;/span&gt;
&lt;span class="c1"&gt;//Create a hash based on the parsed bodyconst hash = crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&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="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Compare the created hash with the value of the X-Shopify-Hmac-Sha256 Headerif (hash === hmacHeader) {&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Notification Requested from Shopify received&lt;/span&gt;&lt;span class="dl"&gt;"&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;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;There is something wrong with this webhook&lt;/span&gt;&lt;span class="dl"&gt;"&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;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, we extract the X-Shopify-Hmac-SHA256 HTTP header from the request, create a hash based on the Hmac-SHA256 algorithm from the request body then compare both hashes.&lt;/p&gt;

&lt;p&gt;Lastly, go ahead and create a constant called secret which would hold the value of the secret Shopify returned to you when created a new webhook connection. You would want to store that as an environmental variable to ensure it is safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Secrets&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Alternatively, you might find platforms with the option to add custom headers to the webhook requests, or a basic authentication header. In that case, you should generate a secret key on your own that you would set as a header and would verify on your end. This is similar to how most API authentication works and is considered secured when used over HTTPS. However, absolutely do not rely on this strategy when using standard HTTP, because any man-in-the-middle could steal your secret and forge authenticated requests&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;IP whitelisting&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If the platform that's publishing webhooks provides a list of IPs that they send requests from, you can set up the receiving endpoint to only accept requests from those IPs. This is generally straightforward, but it depends on the platform that you're hosting on. While IP whitelisting is effective, it can also get cumbersome because you have to maintain the IP whitelist. If the provider adds an IP address, and you fail to update your list, you run the risk of potentially refusing payloads. For that reason, it should be used as a complement to other strategies listed above. Whitelisting IPs is not a requirement to protect yourself against forged requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3) Replay attacks&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Even with the previous two vulnerabilities addressed, you are still susceptible to &lt;a href="https://en.wikipedia.org/wiki/Replay_attack" rel="noopener noreferrer"&gt;replay attacks&lt;/a&gt;. A bad actor could, in theory, intercept an encrypted request with signature verification and simply replay or make identical requests. While he wouldn't be able to see any of the data, nor alter it, he could still cause unintended side-effects such as creating multiple orders after a "Payment Captured" webhooks. This limits the possible consequences of this kind of attack, but nonetheless there are practical solutions to protect yourself against it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Timestamped signatures&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Some webhook providers, like Stripe, will include a timestamp within the signature. The timestamp used at the time of signing will be contained within the header and can be checked against a time window that you determine. Stripe SDK has a default tolerance of 5 minutes. That same timestamp must be appended to the signature before it's hashed.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Idempotency&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Idempotency is a core concept when working with webhooks: if you have already ensured that your endpoints are idempotent, you won't be impacted by a replay attack. By making your endpoint idempotent you ensure that any webhooks are only processed once, even if received multiple times. That's an important case to handle regardless of replay attacks, since most (if not all) providers operate on an at least once delivery strategy. Timestamped signatures are unnecessary if idempotency is correctly implemented - idempotency is the only solution to protect yourself against attacks if your provider does not support timestamped signatures (and most don't).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hookdeck.com/concepts/idempotency" rel="noopener noreferrer"&gt;Learn how to implement idempotency&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Webhooks, when used correctly, are very secure. As with most things, the burden often falls on you to make sure you are properly verifying signatures, using HTTPS and implementing idempotency. Luckily, you're not alone, and the information is available!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hookdeck is a feature-complete webhooks handling platform that handles resilience and monitoring for you. It enables you to decide how to route webhooks requests, debug and visualize your payloads, offer insight into errors and trends, retry on delivery failure, hook into your development process with a local proxy, and much more. Try Hookdeck now for free by creating an account &lt;a href="https://dashboard.hookdeck.com/signup" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>webhooks</category>
      <category>backend</category>
      <category>security</category>
    </item>
    <item>
      <title>Working With Webhooks: Delayed Processing</title>
      <dc:creator>Alexandre Bouchard</dc:creator>
      <pubDate>Thu, 29 Apr 2021 02:28:58 +0000</pubDate>
      <link>https://dev.to/hookdeck/working-with-webhooks-delayed-processing-3poh</link>
      <guid>https://dev.to/hookdeck/working-with-webhooks-delayed-processing-3poh</guid>
      <description>&lt;p&gt;&lt;em&gt;The series “Working With Webhooks” explores the most important concepts to consider when receiving incoming webhooks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a developer, ensuring your back-end works smoothly is key to delivering reliable services. If you don’t ingest and process your webhooks properly, you risk poor performance and server outages, which can negatively impact your product, your users, and your team.&lt;/p&gt;

&lt;p&gt;In this language-agnostic reference guide, you’ll learn to take your webhook game to the next level by implementing &lt;strong&gt;delayed processing&lt;/strong&gt;, one of the most important concepts when building reliable webhooks. Along the way, we’ll cover the basics of ingestion, queueing, processing, retries, and alerts.&lt;/p&gt;

&lt;p&gt;After reading this guide, you’ll have a solid understanding of what it takes to build a robust and well-designed webhook infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The bare-bones approach to webhooks&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Webhooks are considered asynchronous by nature. This is because no specific response is expected from your server, besides a receipt confirmation.&lt;/p&gt;

&lt;p&gt;Developers who are just getting started with webhooks will often set up their handlers in a very simple way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive the request.&lt;/li&gt;
&lt;li&gt;Perform some method.&lt;/li&gt;
&lt;li&gt;Return once the method is completed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While this might seem like a solid approach to handling webhooks, it has several weaknesses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You don't control the rate at which you receive webhooks.&lt;/strong&gt;The platform sending webhooks to your server might deliver a burst of requests all at once. This could be due to bulk actions taken on your end (like uploading a new email list to an email service), or it could be because the platform has accumulated a backlog of webhooks to send after an outage. Whatever the cause, you can see how a large volume of requests in a short span of time might overwhelm a system built using a bare-bones approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn’t account for errors.&lt;/strong&gt;Your webhook handling method might run into an error because it's running out of resources, depends on a third party that is returning an error, or is simply a bad deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn’t account for outages.&lt;/strong&gt;Outages happen, and your webhook infrastructure needs to be prepared for them. You can’t assume your HTTP endpoint will be listening when you receive the webhook. In the case of an outage, you still need to be able to deliver consistent results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn’t account for timeout windows.&lt;/strong&gt;Many platforms offer small timeout windows on the request. If your server fails to perform the work within that window, the connection will be terminated. In the overly simplistic approach outlined above, the method you perform upon receiving the inbound webhook must complete before that window closes, or the requesting platform may report a failure – &lt;em&gt;even if the method eventually succeeds&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ve decided that the bare-bones approach isn’t for you. Good instincts. So, how should you design your webhook infrastructure? This is where delayed processing for webhooks comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is delayed webhook processing?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Most of the time, webhooks can be handled easily. But if your server receives too many requests at once, it can become overloaded. Add all the other unrelated jobs your server handles to that ballooning volume of requests, and you may have a service outage on your hands.&lt;/p&gt;

&lt;p&gt;A well-designed webhook system will handle these volume spikes efficiently and process all inbound requests, regardless of volume. To that end, your server should perform the &lt;em&gt;bare minimum amount of work upon receiving a webhook&lt;/em&gt;. We call this &lt;strong&gt;delayed processing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A server that follows the principles of delayed processing will first return an HTTP 200 response, &lt;em&gt;then&lt;/em&gt; queue the event to be processed asynchronously by your system.&lt;/p&gt;

&lt;p&gt;Utilizing delayed processing has several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It limits the number of resources necessary to process a request.&lt;/li&gt;
&lt;li&gt;It gives you more capacity to handle multiple concurrent requests.&lt;/li&gt;
&lt;li&gt;It allows you the freedom to employ processing-intensive rules and logic on inbound webhooks, without affecting the reliability of your infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to implement delayed processing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A webhook infrastructure with delayed processing must successfully implement three key steps: &lt;strong&gt;ingestion&lt;/strong&gt;, &lt;strong&gt;queuing&lt;/strong&gt; and &lt;strong&gt;processing&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Ingestion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our only goal here is to expose a simple HTTP POST endpoint. This endpoint must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receive webhooks&lt;/li&gt;
&lt;li&gt;Store the request headers &amp;amp; body in a queue&lt;/li&gt;
&lt;li&gt;Return an HTTP 200 status code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The endpoint should be built to utilize as few resources as possible, which will ensure it remains scalable.&lt;/p&gt;

&lt;p&gt;Serverless services like &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lamda&lt;/a&gt;, &lt;a href="https://cloud.google.com/functions" rel="noopener noreferrer"&gt;Google Cloud Functions&lt;/a&gt;, and &lt;a href="https://cloud.google.com/run" rel="noopener noreferrer"&gt;Google Cloud Run&lt;/a&gt; are a great fit for ingestion, since they scale very easily based on the volume of concurrent requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Queuing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once we’ve successfully received the request, we need to keep track of it to process at a later time. The best solution here is to implement a queue.&lt;/p&gt;

&lt;p&gt;However you design your queue, make sure it won't crumble under the load. Most queues are limited by the number of concurrent connections, or throughput in GB/s – you want to make sure that your queueing system can keep pace with any unexpected spikes in volume. Some queues like &lt;a href="https://aws.amazon.com/sqs/" rel="noopener noreferrer"&gt;AWS SQS&lt;/a&gt; and &lt;a href="https://cloud.google.com/pubsub" rel="noopener noreferrer"&gt;PubSub&lt;/a&gt; are virtually limitless, making them great candidates for this use case.&lt;/p&gt;

&lt;p&gt;One good strategy to save on costs is to store the webhook headers and bodies in a file on AWS S3 or GCP Cloud Storage and queue a reference to the file instead of the actual contents.&lt;/p&gt;

&lt;p&gt;Depending on the nature of your use case, you might have to have multiple queues for different webhook topics or types. Some request types will naturally be more important than others. By introducing multiple queues, you can assign different priorities to each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Processing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once you have your webhooks in a queue, you can safely process them. Just be sure to do so at a rate that is reasonable for your infrastructure and use case. Different queues take different approaches to processing – but, generally speaking, you will want a set of worker services, each pulling from the queue at its own pace.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to handle retries&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A webhook queueing system is a great way to ingest and process webhook requests. But even the best queueing systems can drop requests when faced with server overload or other network issues. When this happens, your system won’t complete the underlying transaction or event notification, which can lead to a poor user experience. To handle such cases, we need to implement a retry feature that triggers on two scenarios: &lt;strong&gt;ingestion failure&lt;/strong&gt; and &lt;strong&gt;processing failure.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Retry on ingestion failure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If you fail to ingest a webhook request, you'll need to rely on your provider retry policy. Most platforms have some form of retry strategy. If the platform doesn't offer retries, or you fail to receive all retries, you will need to reconcile your data by pulling the provider API. Since this is not always possible, the reliability of your ingestion service is critical.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Retry on processing failure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;It's virtually guaranteed that, at one point or another, you will run into errors when processing your queue. Thankfully you can leverage your queue to perform retries by properly acknowledging (or not acknowledging) the messages.&lt;/p&gt;

&lt;p&gt;Don't forget to also include a dead letter policy to set aside messages that fail to process multiple times in a row. If you don’t, you may end up jamming the queue with events that are unprocessable.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to handle alerts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You need to know when things aren't going as planned in your production environment. When it comes to delayed processing of webhooks, there are two places where alerts become critical.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;When ingesting&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If your ingestion service responds with an error, this means you are actively missing webhooks. You will want to monitor HTTP responses from your endpoint, and set up an alert if the status isn't a 2xx. Some API providers will also offer some form of alerting if your endpoint doesn’t return a successful response.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;When sending to a dead letter queue&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If you fail to process a message multiple times in a row, you'll want to inspect it so you can determine the root cause of the failure. You will want to monitor the depth of your dead letter queue to alert you when new messages appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Get started with delayed webhook processing in Hookdeck&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you’re looking for a robust platform to handle all the above complexity for you, Hookdeck has your back. You can easily set up and monitor your webhooks without worrying about ingestion, queuing, or troubleshooting processes in your workflow. And it’s free!&lt;/p&gt;

&lt;p&gt;To test-drive these features and more, &lt;a href="https://dashboard.hookdeck.io/signup" rel="noopener noreferrer"&gt;sign up today&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>api</category>
      <category>backend</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Working With Webhooks: Implementing Idempotency</title>
      <dc:creator>Alexandre Bouchard</dc:creator>
      <pubDate>Mon, 26 Apr 2021 16:35:22 +0000</pubDate>
      <link>https://dev.to/hookdeck/working-with-webhooks-implementing-idempotency-1l4i</link>
      <guid>https://dev.to/hookdeck/working-with-webhooks-implementing-idempotency-1l4i</guid>
      <description>&lt;p&gt;Most webhook providers operate on an "at least once" delivery guarantee. The key phrase here is "at least" — you will eventually get the same webhook multiple times. Your application needs to be built to handle those scenarios&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is idempotency?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In computing, when repeating the same action results in the same outcome, we call it idempotent. One common example you have probably encountered is the HTTP &lt;code&gt;PUT&lt;/code&gt; vs the HTTP &lt;code&gt;POST&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;The distinction between the two is that &lt;code&gt;PUT&lt;/code&gt; denotes that the action is idempotent. Updating an inventory count, a profile first name, or assigning an order to a customer can be done multiple times in a row without the use of new or extra resources.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;POST&lt;/code&gt;, however, implies side effects. If you create a new order for entry, every time you call the endpoint, a new entry will be created, even if it contains the same properties.&lt;/p&gt;

&lt;p&gt;Because webhooks are standardized around HTTP &lt;code&gt;POST&lt;/code&gt; calls, it's up to you to figure out what is idempotent by nature, versus what needs to be built to be idempotent. In most cases, the burden will fall on you.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When to build for idempotency&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Generally, events that either create a new resource or cause side-effects in other systems are the trickiest to handle. You wouldn't want to create the same order multiple times because you got the same webhook from Shopify twice. You could also be causing side effects, like sending an email when a product runs out of stock, which no one wants to do multiple times.&lt;/p&gt;

&lt;p&gt;Those are cases where you would need to carefully audit your code to look for any areas where idempotency issues could arise, and then build strategies to make those webhook events idempotent.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Enforcing a unique constraint inherited from the event data&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In many cases, you'll have some unique ID that you can leverage to know if you've already performed the action for any given webhook request. For example, if you are indexing orders from a Shopify store on the &lt;code&gt;orders/created&lt;/code&gt; webhook topic, you can use the &lt;code&gt;order_id&lt;/code&gt; from Shopify as a unique property in your database.&lt;/p&gt;

&lt;p&gt;In SQL, you might do something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;shopify_order_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the simplest solution if your database supports unique constraints. If, say, you also want to send an email to the customer, you would perform the INSERT before you send the email. Since you are using that unique constraint to check for idempotency, you will want to perform side effects afterwards. Lastly, make sure to handle the error and return a 2XX status code that corresponds to the unique constraint violation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tracking webhook history and handling status&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In some cases the first strategy won't be available to you, which could be because you aren't storing any of the data. Nonetheless, every provider will include some identifier for the webhook itself. In Shopify, the request contains &lt;code&gt;X-Shopify-Webhook-Id&lt;/code&gt; in the headers. You can leverage that ID to track the status of the webhooks you are receiving.&lt;/p&gt;

&lt;p&gt;Repeated requests for the same webhook will have the same webhook identifier.&lt;/p&gt;

&lt;p&gt;To handle those scenarios, you will want to create a &lt;code&gt;processed_webhooks&lt;/code&gt; table with a unique constraint on the ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;processed_webhooks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing to do when you receive the request is to store it in the table using the webhook unique ID. Once you have successfully completed your method, you can then update the row to a status of &lt;code&gt;COMPLETED&lt;/code&gt;. In the event that you fail to successfully handle it, just remove the row, and allow next attempts.&lt;/p&gt;

&lt;p&gt;You can wrap your webhook calls with a generic method to verify for idempotency. Here's an example using Postgres, Express and NodeJS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processWebhook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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;handler&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;// Extract the unique ID, using Shopify for this exampleconst unique_id = req.headers["X-Shopify-Webhook-Id"];&lt;/span&gt;
&lt;span class="c1"&gt;// Create a new entry for that webhook idawait client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO processed_webhooks (id) VALUES $1&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;unique_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&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;// PostgreSQL code for unique violationif (e.code == "23505") {&lt;/span&gt;
&lt;span class="c1"&gt;// We are already processing or processed this webhook, return silentlyreturn true;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// Call you methodawait handler(req.body);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// Delete the entry on error to make sure the next one isn't ignoredawait client.query("DELETE FROM processed_webhooks WHERE id = $1", [&lt;/span&gt;
      &lt;span class="nx"&gt;unique_id&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;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;/webhooks/order-created&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="c1"&gt;// Wrap your doSomething method to handle your webhookreturn processWebhook(req, doSomething).catch(() =&amp;gt; res.sendStatus(500));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You want to build your application to avoid taking action on the same webhooks received multiple times. To implement idempotency, you can either enforce a unique constraint inherited from the event data or track webhook history and the handling status.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hookdeck is a feature-complete webhooks handling platform that handles resilience and monitoring for you. It enables you to decide how to route webhooks requests, debug and visualize your payloads, offer insight into errors and trends, retry on delivery failure, hook into your development process with a local proxy, and much more. Try Hookdeck now for free by creating an account &lt;a href="https://dashboard.hookdeck.com/signup" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>api</category>
      <category>backend</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Dealing With Webhooks Sucks But There’s Something You Can Do About It</title>
      <dc:creator>Alexandre Bouchard</dc:creator>
      <pubDate>Fri, 17 Apr 2020 00:02:29 +0000</pubDate>
      <link>https://dev.to/alexbouchardd/dealing-with-webhooks-sucks-but-there-s-something-you-can-do-about-it-3kbb</link>
      <guid>https://dev.to/alexbouchardd/dealing-with-webhooks-sucks-but-there-s-something-you-can-do-about-it-3kbb</guid>
      <description>&lt;p&gt;I’ve been working in e-commerce for the last three years dealing with millions of fairly mission-critical webhook events. My key takeaway is that it sucks to deal with multiple APIs incoming webhooks such as Shopify, Stripe &amp;amp; Intercom, and I hate not having an alternative.&lt;/p&gt;

&lt;p&gt;It’s April 2020, we are stuck inside self quarantining, productivity has been high, and it just strikes me as the perfect time to publish my first post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the problem?
&lt;/h2&gt;

&lt;p&gt;Mistakes are human, and so are bugs. Every once in a while, you will introduce errors in your webhook handling methods. Even with the best integration tests, you are probably not prepared for unexpected payload changes (perhaps a stealth API version upgrade) or a platform downtime. You probably have server logs or even better, something like &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; to catch unexpected exceptions in your code. If these are mission-critical webhooks, are you confident you can find and take action on every single webhook you missed? Maybe, but most likely, you’ll spend the day on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  “Queue first, take action later”, they said
&lt;/h3&gt;

&lt;p&gt;I know what you’ll tell me. You aren’t supposed to take action directly on a webhook! That’s right, the “proper way” of dealing with webhooks is to postpone any meaningful processing through the use of queues and to handle errors &amp;amp; retries on your own. Clubhouse.io engineering team wrote a great article on &lt;a href="https://clubhouse.io/blog/more-reliable-webhooks-with-queues/" rel="noopener noreferrer"&gt;this topic&lt;/a&gt;. They are right; you should use queues.&lt;/p&gt;

&lt;p&gt;However, to handle a single webhook safely, you would need (as per Clubhouse) 4 new services (SQS, S3, a Publisher(s) and a Consumer(s)). It depends on your current architecture, but in short, that’s a lot of new tools, a lot of new code, and especially a lot of time. Time that I could better spend building products for our customers.&lt;/p&gt;

&lt;p&gt;If it feels a lot like reinventing the wheel, well, it’s because it is. Solving this particular problem isn’t specific to your business or your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instead, we rely on webhook providers
&lt;/h3&gt;

&lt;p&gt;Let’s be honest, why does almost every platform out there offer an ‘automatic retry’ feature for their webhooks? Because developers generally DO NOT postpone taking action on their webhooks and really, nobody can blame them. The time required to set up queues and related retry logic makes it so that developers, such as myself, are instead relying on the platform to do the work for them.&lt;/p&gt;

&lt;p&gt;Each platform has its own rules and logic. Shopify will retry 19 times over 48 hours and completely delete your webhook subscription once all attempts are exhausted as opposed to Stripe that will attempt to deliver your webhooks for up to three days with exponential backoff. This becomes increasingly complex to deal with if you are using 3, 5, or 10 API’s webhooks, that each have their own rules on when they will retry, when they will alert you and when they will disable your webhooks.&lt;/p&gt;

&lt;p&gt;Credit where credit is due, Stripe does a lot of things right. They provide a useful UI (but no API) to view your events and retry them. That’s the kind of visibility we need, as developers. I’ve saved many hours of troubleshooting because of that feature alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F2000%2F0%2A5tomujnsWF0_1mNm" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F2000%2F0%2A5tomujnsWF0_1mNm" alt="Stripe’s UI to view failed webhooks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stripe’s UI to view failed webhooks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The thing is that you are out of luck if you are using any other platform, Stripe is the only one that I’ve used that provides this level of tooling for developers. Have you encountered other platforms with a similar level of tooling?&lt;/p&gt;

&lt;h2&gt;
  
  
  An alternative?
&lt;/h2&gt;

&lt;p&gt;For my webhook monitoring, I was looking for a solution that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide full visibility over all my webhooks and their status;&lt;/li&gt;
&lt;li&gt;Allow me to take actions on the failures that occurred;&lt;/li&gt;
&lt;li&gt;Will alert me on failures that need looking in into;&lt;/li&gt;
&lt;li&gt;Work the same way, regardless of the platforms in my stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bad news is that I couldn’t find any.&lt;/p&gt;

&lt;p&gt;The good news is that I built it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hookdeck.io/" rel="noopener noreferrer"&gt;Hookdeck&lt;/a&gt; solves these problems by sitting between the API provider and your systems to monitor, distribute and retry all your webhooks. With it, you can trace every webhook event, view the full request and response payloads. You can also standardize the retry and alerting rules across all your webhooks regardless of the provider. This will guarantee that your webhooks behave the same way and be configured to your own use cases. It allows you to take action on any issues or errors that occur with confidence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F2000%2F1%2AkOXe9tkI5XW7OhKcJva6rA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F2000%2F1%2AkOXe9tkI5XW7OhKcJva6rA.jpeg" alt="Hookdeck Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hookdeck — The wheel you won’t have to reinvent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you are wondering how Hookdeck works, here’s a look at the inner workings of our architecture.&lt;/p&gt;

&lt;p&gt;A platform-agnostic solution is possible. Why should it be the developers or the responsibility of the platforms to have those tools? I like to think it’s the missing piece of the puzzle.&lt;/p&gt;

&lt;p&gt;If you would like to spare yourself the hassle I went through, Hookdeck now available.&lt;/p&gt;

&lt;p&gt;What are your thoughts on handling a large volume of mission-critical webhooks?&lt;/p&gt;

&lt;p&gt;Godspeed,&lt;br&gt;
Alex&lt;/p&gt;

&lt;p&gt;Maker of &lt;a href="https://hookdeck.io/" rel="noopener noreferrer"&gt;Hookdeck&lt;/a&gt;  &amp;amp; CTO at &lt;a href="https://fromrachel.com" rel="noopener noreferrer"&gt;Rachel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
