<?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: Yuriy</title>
    <description>The latest articles on DEV Community by Yuriy (@jmelnikov).</description>
    <link>https://dev.to/jmelnikov</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3774460%2Fba1059ff-843b-4688-a8b0-94b721d99ed2.jpeg</url>
      <title>DEV Community: Yuriy</title>
      <link>https://dev.to/jmelnikov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jmelnikov"/>
    <language>en</language>
    <item>
      <title>Webhooks don’t care that your laptop is offline</title>
      <dc:creator>Yuriy</dc:creator>
      <pubDate>Mon, 20 Jul 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/adal-cloud/webhooks-dont-care-that-your-laptop-is-offline-30p1</link>
      <guid>https://dev.to/adal-cloud/webhooks-dont-care-that-your-laptop-is-offline-30p1</guid>
      <description>&lt;p&gt;Local development environments and webhook senders operate on completely different schedules.&lt;/p&gt;

&lt;p&gt;Your application runs when you need it. It may stop when you close your laptop, restart Docker, lose your internet connection, or finish work for the day.&lt;/p&gt;

&lt;p&gt;A webhook provider does not know any of that. When an event occurs, it sends the request.&lt;/p&gt;

&lt;p&gt;So what happens when your local application is unavailable?&lt;/p&gt;

&lt;h2&gt;
  
  
  A tunnel only works while the tunnel is running
&lt;/h2&gt;

&lt;p&gt;Tunneling tools provide a convenient route from the public internet to a local application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook provider → tunnel → local application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well while the tunnel, internet connection, and application are all running.&lt;/p&gt;

&lt;p&gt;If any part of that path becomes unavailable, the request cannot reach your handler. Some providers retry failed webhooks, but they control the retry schedule. You do not decide how many attempts they make, how long they wait, or when they give up.&lt;/p&gt;

&lt;p&gt;A tunnel provides connectivity. It does not necessarily provide durable request storage or deferred delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  You could build the infrastructure yourself
&lt;/h2&gt;

&lt;p&gt;One solution is to rent a VPS and build an intermediate webhook receiver.&lt;/p&gt;

&lt;p&gt;That usually involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configuring DNS and HTTPS;&lt;/li&gt;
&lt;li&gt;running a reverse proxy;&lt;/li&gt;
&lt;li&gt;storing incoming requests;&lt;/li&gt;
&lt;li&gt;implementing retries and deferred delivery;&lt;/li&gt;
&lt;li&gt;setting up logs and monitoring;&lt;/li&gt;
&lt;li&gt;installing security updates;&lt;/li&gt;
&lt;li&gt;keeping the entire system available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could deploy those components to Kubernetes as well.&lt;/p&gt;

&lt;p&gt;That may be appropriate for a production platform, but it is a lot of infrastructure when the original requirement was simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Receive a webhook now and deliver it to my local application later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Separating receipt from delivery
&lt;/h2&gt;

&lt;p&gt;This is the problem we designed &lt;a href="https://adal.cloud/" rel="noopener noreferrer"&gt;Adal&lt;/a&gt; to solve.&lt;/p&gt;

&lt;p&gt;Instead of making the local machine permanently accessible, Adal separates webhook receipt from local delivery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook provider → Adal Server → Adal CLI → local application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An Adal Server provides a permanent public HTTPS endpoint. When it accepts a webhook, it stores the request in the selected region, including its method, path, query parameters, headers, and body.&lt;/p&gt;

&lt;p&gt;When &lt;a href="https://adal.cloud/docs/adal-cli" rel="noopener noreferrer"&gt;Adal CLI&lt;/a&gt; connects, it forwards requests to a configured local Destination such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;strong&gt;Deliver pending requests on connect&lt;/strong&gt; is enabled, requests received while the CLI was offline can be delivered after it reconnects, as long as they are still within the plan’s retention period.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple example
&lt;/h2&gt;

&lt;p&gt;Imagine testing a &lt;code&gt;payment.completed&lt;/code&gt; webhook while traveling.&lt;/p&gt;

&lt;p&gt;The payment provider sends the event while your laptop has no connection. With a standard tunnel, delivery depends on whether the provider retries after you are back online.&lt;/p&gt;

&lt;p&gt;With Adal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The provider sends the webhook to the Adal Server.&lt;/li&gt;
&lt;li&gt;Adal accepts and stores the request.&lt;/li&gt;
&lt;li&gt;Your laptop reconnects and Adal CLI starts or reconnects.&lt;/li&gt;
&lt;li&gt;The CLI forwards the pending request to your local handler.&lt;/li&gt;
&lt;li&gt;You inspect the delivery result in the Adal dashboard.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no need to repeat the test payment just to generate the same event again.&lt;/p&gt;

&lt;p&gt;The same workflow can help when developing bots, testing GitHub events, debugging CRM integrations, or connecting services available only inside a private network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use infrastructure that matches the problem
&lt;/h2&gt;

&lt;p&gt;Tunnels, VPS instances, and Kubernetes all have valid use cases.&lt;/p&gt;

&lt;p&gt;Use a tunnel when you need temporary public access to a running local port. Use a VPS or Kubernetes when you need to host and operate persistent applications.&lt;/p&gt;

&lt;p&gt;But if the problem is receiving webhooks while a local environment is unavailable, you may not need to build an entire delivery system yourself.&lt;/p&gt;

&lt;p&gt;Sometimes you need webhooks—but not Kubernetes.&lt;/p&gt;

&lt;p&gt;You can try the workflow using the &lt;a href="https://adal.cloud/docs/quickstart" rel="noopener noreferrer"&gt;Adal quickstart guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Read the complete article: &lt;a href="https://dev.toFULL_ARTICLE_URL"&gt;When You Need Webhooks but Not Kubernetes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Your Webhook Receiver Is Offline. Now What?</title>
      <dc:creator>Yuriy</dc:creator>
      <pubDate>Mon, 13 Jul 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/adal-cloud/your-webhook-receiver-is-offline-now-what-4g67</link>
      <guid>https://dev.to/adal-cloud/your-webhook-receiver-is-offline-now-what-4g67</guid>
      <description>&lt;p&gt;Webhooks look simple when everything works.&lt;/p&gt;

&lt;p&gt;A service sends an HTTP request, your application receives it, and the event is processed within milliseconds.&lt;/p&gt;

&lt;p&gt;But production networks are rarely that predictable.&lt;/p&gt;

&lt;p&gt;The receiving service may be restarting. A route may disappear for several minutes. An ISP may filter certain traffic. A corporate firewall may prevent inbound connections altogether. And sometimes the service that needs the webhook is running on a developer’s laptop or inside a private network with no public IP address.&lt;/p&gt;

&lt;p&gt;So what happens to the webhook while the receiver is offline?&lt;/p&gt;

&lt;p&gt;If the sender retries long enough, you may be fine. If it does not—or if its retry window ends before your service recovers—the event may never reach your application.&lt;/p&gt;

&lt;p&gt;Let’s look at how to make this delivery path more resilient.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden coupling in a typical webhook integration
&lt;/h2&gt;

&lt;p&gt;A direct webhook integration usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook provider ──────HTTP request──────&amp;gt; Your application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design couples two separate events:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the provider sending the webhook;&lt;/li&gt;
&lt;li&gt;your application being ready to process it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the delivery to succeed, both systems—and the network between them—must be available at the same moment.&lt;/p&gt;

&lt;p&gt;That creates several possible failure points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the receiving application is deploying or restarting;&lt;/li&gt;
&lt;li&gt;DNS resolution fails temporarily;&lt;/li&gt;
&lt;li&gt;the route between networks is unstable;&lt;/li&gt;
&lt;li&gt;the request times out;&lt;/li&gt;
&lt;li&gt;a firewall rejects the connection;&lt;/li&gt;
&lt;li&gt;the receiver is available only from a private network;&lt;/li&gt;
&lt;li&gt;the sender stops retrying before the receiver recovers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The webhook provider may have its own retry policy, but those policies vary. Some services retry for hours, others make only a few attempts, and some treat certain HTTP responses as permanent failures.&lt;/p&gt;

&lt;p&gt;Your application has no control over that behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate receiving from processing
&lt;/h2&gt;

&lt;p&gt;A more resilient design introduces a persistent delivery layer between the webhook provider and the final destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook provider
        │
        │ HTTPS
        ▼
   Adal Server
   ┌───────────────┐
   │ Receive       │
   │ Store         │
   │ Inspect       │
   │ Retry         │
   │ Redeliver     │
   └───────────────┘
        │
        ├── Direct HTTP ──&amp;gt; Public service
        │
        └── Adal CLI ─────&amp;gt; Private or local service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of sending the webhook directly to your application, the provider sends it to a permanent HTTPS endpoint created in Adal.&lt;/p&gt;

&lt;p&gt;Adal receives and stores the request first. Delivery to your application becomes a separate operation that can be retried if the destination is unavailable.&lt;/p&gt;

&lt;p&gt;This changes the failure model.&lt;/p&gt;

&lt;p&gt;Your application no longer needs to be online at the exact moment the webhook is created. It only needs to become available before the webhook’s retention period expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the receiving endpoint
&lt;/h2&gt;

&lt;p&gt;After creating an account in Adal, create a Server in the Servers section.&lt;/p&gt;

&lt;p&gt;The Server receives a permanent HTTPS URL. Use that URL as the webhook endpoint in the sending service.&lt;/p&gt;

&lt;p&gt;The flow then becomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The provider sends a webhook to the Adal Server.&lt;/li&gt;
&lt;li&gt;Adal receives and stores the request.&lt;/li&gt;
&lt;li&gt;The webhook becomes available for inspection and auditing.&lt;/li&gt;
&lt;li&gt;Adal forwards it to one or more configured destinations.&lt;/li&gt;
&lt;li&gt;If delivery fails, Adal performs the configured retry attempts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Destinations are configured separately, so the same incoming webhook can be routed according to the needs of each receiving system.&lt;/p&gt;

&lt;p&gt;There are two delivery options: Direct HTTP and Adal CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Direct HTTP
&lt;/h2&gt;

&lt;p&gt;Direct HTTP is the simplest option when the receiving service already has a public endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provider ──&amp;gt; Adal ──&amp;gt; https://api.example.com/webhooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adal forwards the webhook directly to the configured HTTPS address.&lt;/p&gt;

&lt;p&gt;The request is delivered as received, with one intentional change: Adal identifies itself in the &lt;code&gt;User-Agent&lt;/code&gt; header. This makes the delivery path transparent and helps investigate reports of unwanted or malicious requests.&lt;/p&gt;

&lt;p&gt;Direct HTTP is useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the receiver has a public IP address or hostname;&lt;/li&gt;
&lt;li&gt;the service is reachable from the internet;&lt;/li&gt;
&lt;li&gt;the endpoint occasionally goes offline;&lt;/li&gt;
&lt;li&gt;network routing between the original sender and receiver is unreliable;&lt;/li&gt;
&lt;li&gt;you want stored requests, delivery history, and controlled retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each destination has its own retry configuration. If the receiving service does not respond, Adal makes the configured number of automatic delivery attempts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: a temporarily unavailable payment handler
&lt;/h3&gt;

&lt;p&gt;Imagine that a payment provider sends this event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment.completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payment_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pay_84b7d1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that moment, your payment handler is being deployed and cannot accept requests.&lt;/p&gt;

&lt;p&gt;With direct delivery, the result depends entirely on the payment provider’s retry policy.&lt;/p&gt;

&lt;p&gt;With Adal in the middle, the event is accepted and stored first. Adal then attempts to deliver it to your handler. If the handler recovers during the automatic retry window, delivery continues without requiring the original provider to send the event again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Adal CLI
&lt;/h2&gt;

&lt;p&gt;Not every webhook receiver should be publicly accessible.&lt;/p&gt;

&lt;p&gt;You may need to deliver events to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an application running on a developer’s computer;&lt;/li&gt;
&lt;li&gt;a test environment inside a private network;&lt;/li&gt;
&lt;li&gt;an internal business service;&lt;/li&gt;
&lt;li&gt;a machine behind NAT;&lt;/li&gt;
&lt;li&gt;a service protected by a corporate router;&lt;/li&gt;
&lt;li&gt;a system without a public IP address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opening inbound ports for these systems may be inconvenient, unsafe, or prohibited by network policy.&lt;/p&gt;

&lt;p&gt;Adal CLI handles this by establishing a persistent, encrypted outbound WebSocket connection to Adal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provider ──&amp;gt; Adal &amp;lt;════ outbound WebSocket ════ Adal CLI ──&amp;gt; Local app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the connection is initiated by Adal CLI, you do not need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expose the local application to the internet;&lt;/li&gt;
&lt;li&gt;configure port forwarding;&lt;/li&gt;
&lt;li&gt;open an inbound firewall port;&lt;/li&gt;
&lt;li&gt;assign a public IP address to the machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the Adal Server receives a webhook, it sends the request through the established connection. Adal CLI then forwards it to the local endpoint configured by the user.&lt;/p&gt;

&lt;p&gt;This is especially useful during development, but it can also work for internal services that are intentionally unavailable from the public internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when automatic retries run out?
&lt;/h2&gt;

&lt;p&gt;Automatic retries solve temporary failures, but no retry schedule can continue forever.&lt;/p&gt;

&lt;p&gt;A destination might remain offline longer than expected. A hardware failure may take several hours to repair. A deployment may need to be rolled back manually. An office network may stay disconnected until the next morning.&lt;/p&gt;

&lt;p&gt;If all automatic delivery attempts are exhausted before the receiver returns, Adal keeps the stored webhook available until the end of its retention period.&lt;/p&gt;

&lt;p&gt;Once the destination is working again, you can restart the delivery manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Automatic retries exhausted
            │
            ▼
Webhook remains stored in Adal
            │
     Receiver recovers
            │
            ▼
User starts redelivery manually
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number of previous automatic attempts does not prevent manual redelivery. The only requirement is that the original webhook must still be stored in Adal.&lt;/p&gt;

&lt;p&gt;After the retention period expires, the webhook is permanently deleted. At that point, it can no longer be inspected, recovered, or delivered again.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical recovery scenario
&lt;/h2&gt;

&lt;p&gt;Suppose an internal order-processing service loses its network connection at 10:00.&lt;/p&gt;

&lt;p&gt;During the outage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An e-commerce platform sends several order events.&lt;/li&gt;
&lt;li&gt;Adal receives and stores them.&lt;/li&gt;
&lt;li&gt;Automatic delivery attempts fail because the internal service is offline.&lt;/li&gt;
&lt;li&gt;The retry limit is reached at 10:30.&lt;/li&gt;
&lt;li&gt;The network is restored at 12:00.&lt;/li&gt;
&lt;li&gt;An operator verifies that the order service is healthy.&lt;/li&gt;
&lt;li&gt;The failed deliveries are restarted manually.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The original events can still be delivered because they remain within their retention period.&lt;/p&gt;

&lt;p&gt;Without an intermediate storage layer, recovery might require asking the original platform to resend the events—assuming that it supports manual redelivery and still retains them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design your receiver for duplicate delivery
&lt;/h2&gt;

&lt;p&gt;Reliable delivery and exactly-once processing are not the same thing.&lt;/p&gt;

&lt;p&gt;Any system that retries HTTP requests can potentially deliver the same event more than once. A receiver may successfully process a webhook but fail to return a response before the sender times out. From the sender’s perspective, the result is unknown, so it retries.&lt;/p&gt;

&lt;p&gt;Manual redelivery creates the same possibility.&lt;/p&gt;

&lt;p&gt;Webhook handlers should therefore be idempotent whenever possible.&lt;/p&gt;

&lt;p&gt;A common approach is to store a unique event ID before performing business operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive webhook
      │
      ▼
Extract event ID
      │
      ▼
Was this ID already processed?
      │
   ┌──┴──┐
  Yes    No
   │      │
Return   Process event
2xx      Store event ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified handler might follow this logic:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&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;await&lt;/span&gt; &lt;span class="nx"&gt;eventStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wasProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;return&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Already processed&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;eventStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markAsProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="na"&gt;status&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="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Processed&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;The exact implementation depends on your database and consistency requirements, but the principle is important: retries should not create duplicate payments, orders, emails, or other irreversible side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security considerations
&lt;/h2&gt;

&lt;p&gt;Adding a delivery layer does not remove the need to secure the receiving endpoint.&lt;/p&gt;

&lt;p&gt;Consider the following before going to production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate webhook signatures when the provider supports them.&lt;/li&gt;
&lt;li&gt;Treat all webhook payloads as untrusted input.&lt;/li&gt;
&lt;li&gt;Use HTTPS for public destinations.&lt;/li&gt;
&lt;li&gt;Restrict what the webhook handler is allowed to do.&lt;/li&gt;
&lt;li&gt;Avoid storing secrets in webhook URLs.&lt;/li&gt;
&lt;li&gt;Monitor repeated failures and unexpected delivery patterns.&lt;/li&gt;
&lt;li&gt;Test how signature validation behaves when a request passes through an intermediary.&lt;/li&gt;
&lt;li&gt;Define an appropriate retention period for the sensitivity of your data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adal stores webhook contents in encrypted form. Stored webhooks are available only to the user who owns the corresponding Server.&lt;/p&gt;

&lt;p&gt;Adal does not sell or use webhook contents for unrelated purposes. The data is processed to store requests and deliver them to destinations configured by the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adal is a transport layer, not a policy bypass
&lt;/h2&gt;

&lt;p&gt;There is an important distinction between improving delivery reliability and bypassing network restrictions.&lt;/p&gt;

&lt;p&gt;Adal is designed to transport webhooks between systems with unstable or limited connectivity. It is not intended to circumvent security controls, regional restrictions, or organizational network policies.&lt;/p&gt;

&lt;p&gt;Before connecting an internal system, confirm that the setup is permitted by the network owner or system administrator.&lt;/p&gt;

&lt;p&gt;This is particularly important in corporate environments where outbound connections, third-party data processing, and webhook payload retention may be subject to security or compliance requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a delivery method
&lt;/h2&gt;

&lt;p&gt;The choice between Direct HTTP and Adal CLI depends primarily on whether the receiving service is publicly reachable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Direct HTTP&lt;/th&gt;
&lt;th&gt;Adal CLI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public receiving endpoint&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public IP address&lt;/td&gt;
&lt;td&gt;Usually required&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open inbound port&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works with local development&lt;/td&gt;
&lt;td&gt;Possible with extra networking&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works behind NAT&lt;/td&gt;
&lt;td&gt;Only with forwarding or a proxy&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic retry support&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual redelivery during retention&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;strong&gt;Direct HTTP&lt;/strong&gt; when your service already exposes a public HTTPS endpoint.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;Adal CLI&lt;/strong&gt; when the destination is local, private, behind NAT, or unable to accept inbound internet connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production checklist
&lt;/h2&gt;

&lt;p&gt;Before relying on any webhook delivery pipeline in production, verify the complete failure path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send a test webhook successfully.&lt;/li&gt;
&lt;li&gt;Confirm that the HTTP method, headers, and body arrive as expected.&lt;/li&gt;
&lt;li&gt;Stop the receiving service.&lt;/li&gt;
&lt;li&gt;Confirm that failed delivery attempts are recorded.&lt;/li&gt;
&lt;li&gt;Start the service again during the retry window.&lt;/li&gt;
&lt;li&gt;Verify that automatic delivery resumes.&lt;/li&gt;
&lt;li&gt;Exhaust the automatic retries in a test environment.&lt;/li&gt;
&lt;li&gt;Restart the delivery manually.&lt;/li&gt;
&lt;li&gt;Test duplicate events and confirm idempotent processing.&lt;/li&gt;
&lt;li&gt;Verify signature validation.&lt;/li&gt;
&lt;li&gt;Review the webhook retention period.&lt;/li&gt;
&lt;li&gt;Confirm the setup with the network or security administrator.&lt;/li&gt;
&lt;li&gt;Add monitoring for repeated delivery failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing failure behavior is just as important as testing the successful path. The middle of an outage is a bad time to discover that a retry policy, signature check, or deduplication mechanism does not behave as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The main reliability problem with direct webhooks is timing: the sender and receiver must both be available at the same moment.&lt;/p&gt;

&lt;p&gt;A persistent delivery layer breaks that dependency.&lt;/p&gt;

&lt;p&gt;Adal receives and stores the webhook first, then attempts to deliver it through Direct HTTP or Adal CLI. Temporary failures are handled with automatic retries. Longer outages can be handled with manual redelivery, provided the webhook is still within its retention period.&lt;/p&gt;

&lt;p&gt;That does not eliminate every integration failure. Your handlers still need authentication, validation, monitoring, and idempotency. But it gives you something extremely valuable during an outage: time to restore the receiving service without immediately losing the original request.&lt;/p&gt;

&lt;p&gt;How do you currently handle webhooks when a destination remains offline longer than the sender’s retry window?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>devops</category>
      <category>backend</category>
    </item>
    <item>
      <title>Webhook idempotency: how to handle duplicate deliveries safely</title>
      <dc:creator>Yuriy</dc:creator>
      <pubDate>Mon, 06 Jul 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/adal-cloud/webhook-idempotency-how-to-handle-duplicate-deliveries-safely-1m4</link>
      <guid>https://dev.to/adal-cloud/webhook-idempotency-how-to-handle-duplicate-deliveries-safely-1m4</guid>
      <description>&lt;p&gt;Webhook delivery is usually described as a simple flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provider sends an event → your endpoint receives it → your application processes it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, it is rarely that simple.&lt;/p&gt;

&lt;p&gt;A webhook can be delivered more than once because of timeouts, network failures, retries, process restarts, or an HTTP response that was generated successfully but never reached the sender.&lt;/p&gt;

&lt;p&gt;That means every webhook consumer should answer one question before it becomes an incident:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens if the same webhook is processed twice?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is “we create the same record again,” “we send another email,” or “we charge the customer a second time,” the integration needs idempotency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duplicate delivery is normal
&lt;/h2&gt;

&lt;p&gt;Webhook providers often use at-least-once delivery semantics.&lt;/p&gt;

&lt;p&gt;In practical terms, this means a provider tries to ensure that an event is delivered successfully. If it cannot confirm success, it may send the event again.&lt;/p&gt;

&lt;p&gt;Consider this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. A provider sends a webhook.
2. Your service receives it.
3. Your service creates an order in the database.
4. Your service responds with 200 OK.
5. A network issue prevents the provider from receiving that response.
6. The provider retries the webhook.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the provider’s perspective, the first attempt may have failed.&lt;/p&gt;

&lt;p&gt;From your application’s perspective, the business action has already happened.&lt;/p&gt;

&lt;p&gt;Without protection against duplicates, the retry may create a second order.&lt;/p&gt;

&lt;p&gt;This can lead to duplicate payments, duplicate CRM entities, repeated emails, duplicated jobs, or inconsistent state in downstream systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What idempotency means
&lt;/h2&gt;

&lt;p&gt;An operation is idempotent when performing it multiple times produces the same final state as performing it once.&lt;/p&gt;

&lt;p&gt;For example, setting a user's status is naturally idempotent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT /users/42/status
Content-Type: application/json

{
  "status": "active"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sending that request twice still leaves the user active.&lt;/p&gt;

&lt;p&gt;Creating a payment is different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /payments
Content-Type: application/json

{
  "amount": 100
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an additional mechanism, sending this request twice may create two payment operations.&lt;/p&gt;

&lt;p&gt;Webhook consumers should be designed so that a retry does not repeat the business action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why payload comparison is not reliable
&lt;/h2&gt;

&lt;p&gt;A common first attempt is to compare incoming request bodies.&lt;/p&gt;

&lt;p&gt;For example, store the webhook payload and reject any future request with the same JSON.&lt;/p&gt;

&lt;p&gt;This approach breaks down quickly.&lt;/p&gt;

&lt;p&gt;Two separate events may have identical payloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice.created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These could represent two different invoices with the same amount and currency.&lt;/p&gt;

&lt;p&gt;The same problem applies to payload hashes. A hash makes comparisons easier, but it does not tell you whether two equal payloads represent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the same event delivered again; or&lt;/li&gt;
&lt;li&gt;two different events with the same contents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also implementation questions that become surprisingly difficult:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should timestamps be included?&lt;/li&gt;
&lt;li&gt;Should signatures be included?&lt;/li&gt;
&lt;li&gt;Does JSON key order matter?&lt;/li&gt;
&lt;li&gt;Should query parameters be compared?&lt;/li&gt;
&lt;li&gt;How do you handle binary payloads?&lt;/li&gt;
&lt;li&gt;Which fields are stable across retries?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A payload is data. It is not necessarily an event identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use an explicit idempotency key
&lt;/h2&gt;

&lt;p&gt;The usual solution is an idempotency key: a unique identifier associated with one logical operation.&lt;/p&gt;

&lt;p&gt;The sender includes the key with the request. The receiver stores it and ensures that the same key cannot trigger the same business action twice.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /webhooks/payment
Content-Type: application/json
X-Idempotency-Key: 01989d55-20df-7a72-87f9-6e50f3d78315

{
  "event": "payment.completed",
  "amount": 100
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processing flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Receive the webhook.
2. Read the idempotency key.
3. Atomically reserve or store the key.
4. If the key already exists, treat the request as a duplicate.
5. If the key is new, perform the business action.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key must be handled atomically.&lt;/p&gt;

&lt;p&gt;A simple “check whether it exists, then insert it” flow is not safe under concurrent requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A checks for key → not found
Request B checks for key → not found
Request A creates order
Request B creates order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The uniqueness check belongs in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: PostgreSQL
&lt;/h2&gt;

&lt;p&gt;A minimal schema might look like this:&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;idempotency_key&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;processed_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&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;Then reserve the key with a single statement:&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;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&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;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application can inspect the number of inserted rows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; row inserted: this is the first time the key was seen;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; rows inserted: the key was already processed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, in Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;`
        INSERT INTO processed_webhooks (idempotency_key)
        VALUES ($1)
        ON CONFLICT (idempotency_key) DO NOTHING
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;rowsAffected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowsAffected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rowsAffected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// This webhook was already processed.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Perform the business action here.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real system, you may also want to store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the event type;&lt;/li&gt;
&lt;li&gt;the processing status;&lt;/li&gt;
&lt;li&gt;the response or created entity ID;&lt;/li&gt;
&lt;li&gt;the original request ID;&lt;/li&gt;
&lt;li&gt;failure details;&lt;/li&gt;
&lt;li&gt;timestamps for cleanup and retention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact design depends on whether repeated requests should receive the original result, a generic success response, or a specific “already processed” response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store keys for long enough
&lt;/h2&gt;

&lt;p&gt;Idempotency keys are only useful while they are retained.&lt;/p&gt;

&lt;p&gt;If a provider can retry a webhook several hours or days later, deleting the key after five minutes may reintroduce duplicate processing.&lt;/p&gt;

&lt;p&gt;The retention period should be based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the provider’s retry policy;&lt;/li&gt;
&lt;li&gt;your own retry policy;&lt;/li&gt;
&lt;li&gt;the type of side effect;&lt;/li&gt;
&lt;li&gt;how expensive or dangerous duplicate execution would be;&lt;/li&gt;
&lt;li&gt;compliance and storage requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For financial operations, order creation, provisioning, or external API calls, it is usually better to retain keys longer than the minimum expected retry window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency is not only for payments
&lt;/h2&gt;

&lt;p&gt;Idempotency keys are often associated with payment APIs, but they are useful anywhere a request can cause a side effect.&lt;/p&gt;

&lt;p&gt;Typical examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating users or accounts;&lt;/li&gt;
&lt;li&gt;provisioning infrastructure;&lt;/li&gt;
&lt;li&gt;sending emails or messages;&lt;/li&gt;
&lt;li&gt;updating CRM records;&lt;/li&gt;
&lt;li&gt;triggering deployments;&lt;/li&gt;
&lt;li&gt;creating support tickets;&lt;/li&gt;
&lt;li&gt;synchronizing data to external systems;&lt;/li&gt;
&lt;li&gt;scheduling jobs;&lt;/li&gt;
&lt;li&gt;processing queue messages;&lt;/li&gt;
&lt;li&gt;handling webhook events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more expensive or irreversible the action is, the more important duplicate protection becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding idempotency keys with Adal
&lt;/h2&gt;

&lt;p&gt;Not every webhook provider sends a suitable event ID or idempotency key.&lt;/p&gt;

&lt;p&gt;Some providers include an event ID in the payload. Others expose it through a header. Some do not provide a stable identifier at all.&lt;/p&gt;

&lt;p&gt;Adal can add an idempotency key when forwarding a webhook to a destination.&lt;/p&gt;

&lt;p&gt;When enabled, Adal adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-Adal-Idempotency: &amp;lt;unique-key&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setting is configured separately for each destination.&lt;/p&gt;

&lt;p&gt;This matters because some consumers may require the original request to remain unchanged, while others benefit from an explicit delivery identity.&lt;/p&gt;

&lt;p&gt;The key belongs to one request in Adal.&lt;/p&gt;

&lt;p&gt;That gives retries and replays intentionally different behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Automatic retry of the same request
→ same X-Adal-Idempotency value

Manual retry of the same delivery
→ same X-Adal-Idempotency value

Replay of an older webhook
→ new X-Adal-Idempotency value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;An automatic retry is another technical attempt to deliver the same logical request. It should carry the same idempotency key.&lt;/p&gt;

&lt;p&gt;A replay is an explicit decision to create a new request from an older webhook. Even when the payload is identical, it represents a new action and receives a new key.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical scenario
&lt;/h2&gt;

&lt;p&gt;Imagine a webhook that creates a customer and an order in your CRM.&lt;/p&gt;

&lt;p&gt;Without idempotency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Adal delivers the webhook.
2. Your service creates the customer and order.
3. Your service returns 200 OK.
4. The response is lost because of a network failure.
5. Adal retries.
6. Your service creates another customer and another order.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;X-Adal-Idempotency&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Adal delivers the webhook with X-Adal-Idempotency.
2. Your service reserves the key in the database.
3. Your service creates the customer and order.
4. The response is lost.
5. Adal retries using the same key.
6. Your service sees that the key already exists.
7. No duplicate customer or order is created.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delivery can be retried until the sender receives confirmation, while the business action is executed once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Duplicate webhooks are not an exceptional failure mode. They are a normal part of building reliable systems over unreliable networks.&lt;/p&gt;

&lt;p&gt;A webhook consumer should not rely on exactly-once delivery unless the provider explicitly guarantees it end to end — and even then, downstream side effects may still need protection.&lt;/p&gt;

&lt;p&gt;Do not use payload equality as event identity. Two different events can look identical, and the same event can be delivered multiple times.&lt;/p&gt;

&lt;p&gt;Use an explicit idempotency key, enforce uniqueness atomically, and design the consumer so repeated delivery does not repeat the side effect.&lt;/p&gt;

&lt;p&gt;That makes retries safe, debugging easier, and webhook integrations much more predictable.&lt;/p&gt;




&lt;p&gt;Adal is a webhook delivery and observability platform. It can optionally add &lt;code&gt;X-Adal-Idempotency&lt;/code&gt; to forwarded requests on a per-destination basis, helping webhook consumers safely distinguish retries from new requests.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>backend</category>
      <category>api</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Webhooks get lost and why they sometimes arrive twice</title>
      <dc:creator>Yuriy</dc:creator>
      <pubDate>Mon, 29 Jun 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/adal-cloud/why-webhooks-get-lost-and-why-they-sometimes-arrive-twice-429b</link>
      <guid>https://dev.to/adal-cloud/why-webhooks-get-lost-and-why-they-sometimes-arrive-twice-429b</guid>
      <description>&lt;p&gt;Webhooks often look deceptively simple.&lt;/p&gt;

&lt;p&gt;One service sends an HTTP request. Another service receives it. The receiver returns &lt;code&gt;200 OK&lt;/code&gt;, processes the payload, and everyone moves on.&lt;/p&gt;

&lt;p&gt;That model works until the webhook becomes part of a real business process.&lt;/p&gt;

&lt;p&gt;A missed event can mean a customer message that never reaches a chatbot, an order that does not appear in a CRM, a subscription that is not updated, or a payment that succeeds but does not unlock access for the customer.&lt;/p&gt;

&lt;p&gt;But lost webhooks are only half of the problem. The other half is duplicate delivery. A reliable webhook integration must be prepared for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it mean to lose a webhook?
&lt;/h2&gt;

&lt;p&gt;A webhook is lost when an event is sent but never successfully processed by the receiving system.&lt;/p&gt;

&lt;p&gt;There are many possible causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the receiving server is temporarily unavailable;&lt;/li&gt;
&lt;li&gt;a reverse proxy is misconfigured;&lt;/li&gt;
&lt;li&gt;the application crashes after accepting the request;&lt;/li&gt;
&lt;li&gt;the database is overloaded;&lt;/li&gt;
&lt;li&gt;a TLS certificate expires;&lt;/li&gt;
&lt;li&gt;a deployment introduces an unexpected error;&lt;/li&gt;
&lt;li&gt;a DNS or network change routes traffic to infrastructure that is no longer active.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a low-risk notification, this may be inconvenient but manageable. For a production integration, it can become a business problem.&lt;/p&gt;

&lt;p&gt;Imagine a payment provider sends an event confirming that a customer has paid. If your application never processes that event, the customer may be charged but never receive access to the product.&lt;/p&gt;

&lt;p&gt;That is not just a technical failure. It creates support work, refunds, manual reconciliation, and a loss of trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS migrations can create both loss and duplication
&lt;/h2&gt;

&lt;p&gt;A common example is moving an application to a new server.&lt;/p&gt;

&lt;p&gt;The usual approach looks simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy the application to a new server.&lt;/li&gt;
&lt;li&gt;Update the DNS record.&lt;/li&gt;
&lt;li&gt;Wait for traffic to move.&lt;/li&gt;
&lt;li&gt;Shut down the old server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem is that DNS does not update everywhere at the same moment.&lt;/p&gt;

&lt;p&gt;Resolvers, ISPs, corporate networks, and individual machines may cache the old address for different amounts of time. Even when the DNS TTL is low, some traffic can continue reaching the old server after the record has been changed.&lt;/p&gt;

&lt;p&gt;During the transition, different webhook senders may reach different servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook provider
        |
        +--&amp;gt; old server
        |
        +--&amp;gt; new server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the old server is already offline, webhooks sent there may fail or be lost. If both servers are active, the same event can potentially be processed twice. That creates a different class of failure.&lt;/p&gt;

&lt;p&gt;A chatbot might send two replies to the same message. A CRM integration might create duplicate records. A payment workflow might credit a balance twice or issue the same product twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why retries are necessary
&lt;/h2&gt;

&lt;p&gt;Webhook delivery should normally use an at-least-once model.&lt;/p&gt;

&lt;p&gt;That means a sender retries delivery when it cannot confirm that the receiving system accepted the event. This is much safer than silently dropping an event after one failed attempt.&lt;/p&gt;

&lt;p&gt;However, at-least-once delivery has an important consequence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The receiving system must assume that the same event can arrive more than once.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A sender may retry because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the destination returned a &lt;code&gt;5xx&lt;/code&gt; response;&lt;/li&gt;
&lt;li&gt;the connection timed out;&lt;/li&gt;
&lt;li&gt;the destination was temporarily unavailable;&lt;/li&gt;
&lt;li&gt;the sender did not receive the response, even though the receiver processed the request;&lt;/li&gt;
&lt;li&gt;the provider intentionally retries to protect against transient network failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is normal behaviour. It is not necessarily a bug in the provider. The receiving side must be designed accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency is the protection against duplicates
&lt;/h2&gt;

&lt;p&gt;The key concept is &lt;strong&gt;idempotency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An idempotent operation produces the same final result whether it runs once or several times.&lt;/p&gt;

&lt;p&gt;For webhook processing, this usually means storing the provider’s event identifier before running the business logic.&lt;/p&gt;

&lt;p&gt;A simplified flow might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive webhook
        |
        v
Validate signature
        |
        v
Check event ID
        |
        +--&amp;gt; already processed --&amp;gt; return 200 OK
        |
        v
Store event ID
        |
        v
Run business logic
        |
        v
Return 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, if a payment provider sends an event with the ID &lt;code&gt;evt_123&lt;/code&gt;, your application should record that ID after receiving it.&lt;/p&gt;

&lt;p&gt;If the same event arrives again, the application should recognise it as a duplicate and avoid repeating the business operation.&lt;/p&gt;

&lt;p&gt;Returning &lt;code&gt;200 OK&lt;/code&gt; is still important: it tells the sender that the event has been safely handled, even when no additional action was needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledge quickly, process safely
&lt;/h2&gt;

&lt;p&gt;Another useful pattern is to separate acceptance from processing.&lt;/p&gt;

&lt;p&gt;Instead of performing all business logic directly inside the webhook request handler, the application can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the request.&lt;/li&gt;
&lt;li&gt;Store the event durably.&lt;/li&gt;
&lt;li&gt;Return a successful response quickly.&lt;/li&gt;
&lt;li&gt;Process the event asynchronously.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This reduces the risk of timeouts and makes failures easier to recover from. It also gives you a durable record of what happened, which is essential when investigating an incident.&lt;/p&gt;

&lt;p&gt;Of course, this only works if the storage step itself is reliable. Returning &lt;code&gt;200 OK&lt;/code&gt; before the event has been saved somewhere durable can still cause data loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical webhook reliability checklist
&lt;/h2&gt;

&lt;p&gt;A production webhook integration should answer these questions clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens when the destination is temporarily unavailable?&lt;/li&gt;
&lt;li&gt;Are failed deliveries retried?&lt;/li&gt;
&lt;li&gt;How long are retries performed?&lt;/li&gt;
&lt;li&gt;Can the same event be delivered more than once?&lt;/li&gt;
&lt;li&gt;Does the application use event IDs or idempotency keys?&lt;/li&gt;
&lt;li&gt;Are incoming requests stored before expensive processing starts?&lt;/li&gt;
&lt;li&gt;Can the delivery history be inspected later?&lt;/li&gt;
&lt;li&gt;Can a failed event be replayed safely?&lt;/li&gt;
&lt;li&gt;During a migration, how long will the old endpoint remain available?&lt;/li&gt;
&lt;li&gt;Can the team distinguish between an event that was never received and one that was received but failed during processing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these questions do not have explicit answers, reliability depends too heavily on everything going right.&lt;/p&gt;

&lt;p&gt;And in production, everything eventually does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability is not just an HTTP status code
&lt;/h2&gt;

&lt;p&gt;Returning &lt;code&gt;200 OK&lt;/code&gt; is not the same as handling an event reliably.&lt;/p&gt;

&lt;p&gt;Reliable webhook processing requires a clear delivery model, retry behaviour, idempotent business logic, durable event storage, and enough visibility to understand what happened when an incident occurs.&lt;/p&gt;

&lt;p&gt;That is why webhook infrastructure matters.&lt;/p&gt;

&lt;p&gt;Adal Cloud is designed to make webhook delivery predictable and observable: incoming requests are retained, delivery attempts can be inspected, failed deliveries can be retried, and stored requests can be replayed when needed.&lt;/p&gt;

&lt;p&gt;When webhooks carry events that matter to customers, payments, or business operations, the delivery path should not be the least reliable part of the system.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>webhooks</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why I made Adal CLI Open Source</title>
      <dc:creator>Yuriy</dc:creator>
      <pubDate>Wed, 24 Jun 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/adal-cloud/why-i-made-adal-cli-open-source-n6f</link>
      <guid>https://dev.to/adal-cloud/why-i-made-adal-cli-open-source-n6f</guid>
      <description>&lt;p&gt;When you build an app that runs on someone else’s machine, trust becomes a very practical issue.&lt;/p&gt;

&lt;p&gt;It is not just about branding, positioning, or saying the right things on a landing page. The user is allowing your software to run in their local environment. Depending on what the app does, that environment may include files, terminal commands, configuration, credentials, or work projects.&lt;/p&gt;

&lt;p&gt;So the question becomes simple: how can a user know that the app is doing only what it is supposed to do?&lt;/p&gt;

&lt;p&gt;For Adal CLI, I decided that the answer should not be “just trust me.”&lt;/p&gt;

&lt;p&gt;It should be verifiable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local apps need a different level of trust
&lt;/h2&gt;

&lt;p&gt;A web app usually runs on infrastructure controlled by the product team. A local app is different. It runs directly on the user’s device.&lt;/p&gt;

&lt;p&gt;That changes the trust model.&lt;/p&gt;

&lt;p&gt;If a CLI tool reads files, sends requests, modifies configuration, or interacts with local projects, users have every right to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What exactly does it read?&lt;/li&gt;
&lt;li&gt;What does it send over the network?&lt;/li&gt;
&lt;li&gt;Does it collect anything unnecessary?&lt;/li&gt;
&lt;li&gt;Can I build it myself instead of downloading a binary?&lt;/li&gt;
&lt;li&gt;What happens if the project stops being maintained?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are fair questions. And for developer tools, they are especially important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source makes trust verifiable
&lt;/h2&gt;

&lt;p&gt;This is one of the main reasons I made Adal CLI open source.&lt;/p&gt;

&lt;p&gt;Open source does not magically make a project secure or trustworthy. But it changes the relationship between the developer and the user.&lt;/p&gt;

&lt;p&gt;Instead of asking people to believe a claim, it gives them a way to verify it.&lt;/p&gt;

&lt;p&gt;Anyone can inspect the code and see what the app actually does. If someone does not want to use a prebuilt binary, they can build the project from source. If a developer wants to review how data is handled, they can do that. If someone finds a problem, they can open an issue or submit a pull request.&lt;/p&gt;

&lt;p&gt;That possibility matters, even if most users never read the source code themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  It also makes development more transparent
&lt;/h2&gt;

&lt;p&gt;Another benefit of open source is that people can see how the project evolves.&lt;/p&gt;

&lt;p&gt;They can look at the changelog, commits, issues, and pull requests. They can see which bugs were fixed, which features were added, and how decisions were made over time.&lt;/p&gt;

&lt;p&gt;For me, that transparency is important. A local tool should not feel like a black box.&lt;/p&gt;

&lt;p&gt;This is especially true for CLI tools, because they often operate close to the developer’s actual work: repositories, config files, scripts, and terminal workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source is not a shortcut
&lt;/h2&gt;

&lt;p&gt;There is one important caveat: publishing the code is not enough.&lt;/p&gt;

&lt;p&gt;Open source is not a substitute for responsibility. A project can be open and still be confusing, insecure, poorly documented, or hard to build.&lt;/p&gt;

&lt;p&gt;So if open source is meant to support trust, it should come with the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clear documentation;&lt;/li&gt;
&lt;li&gt;reproducible builds;&lt;/li&gt;
&lt;li&gt;a changelog;&lt;/li&gt;
&lt;li&gt;a license;&lt;/li&gt;
&lt;li&gt;instructions for building from source;&lt;/li&gt;
&lt;li&gt;an explanation of what data the app uses and why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without those things, “the code is open” can become more of a slogan than a real advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing dependency on one maintainer
&lt;/h2&gt;

&lt;p&gt;There is another reason I care about this: sustainability.&lt;/p&gt;

&lt;p&gt;If a tool becomes useful to people, they should not be completely dependent on one person maintaining it forever.&lt;/p&gt;

&lt;p&gt;With an open source project, the community can fork it, fix critical issues, adapt it to specific needs, or continue development if the original maintainer slows down or moves on.&lt;/p&gt;

&lt;p&gt;That does not guarantee anything, of course. But it gives the project a better chance to survive beyond a single author.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Not every app has to be open source.&lt;/p&gt;

&lt;p&gt;But if you are building something that runs locally, touches files, works with terminal commands, reads configuration, or handles sensitive data, openness can become one of the strongest arguments for trust.&lt;/p&gt;

&lt;p&gt;Trust is hard to earn through promises.&lt;/p&gt;

&lt;p&gt;It is much easier to build through transparency, verifiability, and respect for the user.&lt;/p&gt;

&lt;p&gt;That is why Adal CLI is open source.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>privacy</category>
      <category>cli</category>
      <category>security</category>
    </item>
    <item>
      <title>When you just need to inspect a webhook before writing code</title>
      <dc:creator>Yuriy</dc:creator>
      <pubDate>Sat, 13 Jun 2026 04:16:51 +0000</pubDate>
      <link>https://dev.to/adal-cloud/when-you-just-need-to-inspect-a-webhook-before-writing-code-5efd</link>
      <guid>https://dev.to/adal-cloud/when-you-just-need-to-inspect-a-webhook-before-writing-code-5efd</guid>
      <description>&lt;p&gt;Webhooks are usually discussed as something your application should process automatically.&lt;/p&gt;

&lt;p&gt;A payment provider sends an event. A Git hosting service notifies you about a push. A SaaS product sends a status update. Your backend receives the request, verifies it, stores it, and runs the required logic.&lt;/p&gt;

&lt;p&gt;But before all of that, there is often a much simpler step: you just need to see what the webhook actually looks like. Not the example from the documentation. Not the expected payload in your head. The real request.&lt;/p&gt;

&lt;p&gt;What method does it use? Which headers are included? How is the body structured? Where is the event type stored? Are there signatures, timestamps, nested fields, IDs, or provider-specific details that are not obvious from the docs?&lt;/p&gt;

&lt;p&gt;For this first step, setting up a local server can be unnecessary work.&lt;/p&gt;

&lt;p&gt;You can use a tunneling tool, expose your local application, write a temporary handler, log the request, format the output, and then inspect the result. That works, but sometimes it is too much for a simple question: what exactly does this service send?&lt;/p&gt;

&lt;p&gt;This is one of the scenarios where Adal can help.&lt;/p&gt;

&lt;p&gt;Adal gives you a permanent webhook endpoint URL. You can paste that URL into the webhook settings of a service like Stripe, GitHub, Slack, Dodo Payments, or any other provider that sends webhook events.&lt;/p&gt;

&lt;p&gt;When the webhook arrives, Adal stores the request and shows it in the interface.&lt;/p&gt;

&lt;p&gt;You can inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP method&lt;/li&gt;
&lt;li&gt;path&lt;/li&gt;
&lt;li&gt;headers&lt;/li&gt;
&lt;li&gt;body&lt;/li&gt;
&lt;li&gt;payload structure&lt;/li&gt;
&lt;li&gt;request size&lt;/li&gt;
&lt;li&gt;delivery details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is useful before you write the real backend handler.&lt;/p&gt;

&lt;p&gt;You can send a test webhook, inspect the actual request, compare it with the documentation, and only then decide how your application should process it.&lt;/p&gt;

&lt;p&gt;This is also useful when debugging. Sometimes the problem is not in your handler at all. The webhook may not be reaching the expected URL. The payload may not match the event you selected. A header may be missing. A signature may be different from what you expected.&lt;/p&gt;

&lt;p&gt;Having the raw request visible makes this much easier to understand.&lt;/p&gt;

&lt;p&gt;This does not replace proper webhook processing. You still need your backend, validation, signature verification, retries, idempotency, and business logic for production workflows.&lt;/p&gt;

&lt;p&gt;But before that, there is a very practical first step: receive the webhook, look at it and understand what was actually sent.&lt;/p&gt;

&lt;p&gt;That is the small use case Adal is designed to make simple.&lt;/p&gt;

&lt;p&gt;No temporary server just to inspect a payload. No manual log formatting. No guessing based only on documentation. Just a real incoming webhook, shown in a way that is easy to inspect before you write the code that handles it.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building Adal: predictable webhook delivery without black boxes</title>
      <dc:creator>Yuriy</dc:creator>
      <pubDate>Thu, 11 Jun 2026 16:29:50 +0000</pubDate>
      <link>https://dev.to/adal-cloud/building-adal-predictable-webhook-delivery-without-black-boxes-4an</link>
      <guid>https://dev.to/adal-cloud/building-adal-predictable-webhook-delivery-without-black-boxes-4an</guid>
      <description>&lt;p&gt;Hi DEV community,&lt;/p&gt;

&lt;p&gt;I'm Yuriy, a fullstack developer and DevOps engineer with 10+ years of experience. I'm currently building Adal — a webhook delivery and observability platform for developers.&lt;/p&gt;

&lt;p&gt;The idea came from a simple frustration: webhook delivery often feels like a black box.&lt;/p&gt;

&lt;p&gt;A provider sends a request. Something fails. Maybe the endpoint was down, maybe the payload was different than expected, maybe the retry happened later, maybe the logs are incomplete. As developers, we often end up guessing instead of debugging from clear facts.&lt;/p&gt;

&lt;p&gt;Adal is my attempt to make webhook delivery more predictable.&lt;/p&gt;

&lt;p&gt;The core principles are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;permanent endpoint URLs;&lt;/li&gt;
&lt;li&gt;full request visibility;&lt;/li&gt;
&lt;li&gt;delivery logs that explain what happened;&lt;/li&gt;
&lt;li&gt;replay without waiting for the provider;&lt;/li&gt;
&lt;li&gt;retries that are visible and controlled;&lt;/li&gt;
&lt;li&gt;no hidden transformations;&lt;/li&gt;
&lt;li&gt;infrastructure that can be inspected and reasoned about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm building Adal as a practical developer tool, but also as an engineering experiment: how much of webhook infrastructure can be made explicit, observable, and boring in a good way?&lt;/p&gt;

&lt;p&gt;The stack is mostly Go, PostgreSQL, Redis, React, Docker, Cloudflare R2, and a regional architecture for receiving and delivering webhook requests. The CLI is open source and written in Go.&lt;/p&gt;

&lt;p&gt;On this DEV organization page, I plan to write about the technical side of building Adal: webhook infrastructure, backend reliability, regional delivery, observability, product decisions, failures, trade-offs, and lessons from building a SaaS from scratch.&lt;/p&gt;

&lt;p&gt;This is still an early-stage project, but it is already public and usable.&lt;/p&gt;

&lt;p&gt;If you work with webhooks, build integrations, or care about predictable backend systems, I'd be happy to hear your feedback.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>saas</category>
      <category>webhooks</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
