<?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: Jafar on Payments</title>
    <description>The latest articles on DEV Community by Jafar on Payments (@jafaronpayments).</description>
    <link>https://dev.to/jafaronpayments</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%2F4116896%2Fddab5f88-3c0d-43ca-8b33-633b269dd3cd.png</url>
      <title>DEV Community: Jafar on Payments</title>
      <link>https://dev.to/jafaronpayments</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jafaronpayments"/>
    <language>en</language>
    <item>
      <title>How to Verify Payment Webhooks in Node.js (So You Never Ship an Order Twice)</title>
      <dc:creator>Jafar on Payments</dc:creator>
      <pubDate>Thu, 05 Nov 2026 15:01:56 +0000</pubDate>
      <link>https://dev.to/jafaronpayments/how-to-verify-payment-webhooks-in-nodejs-so-you-never-ship-an-order-twice-2io9</link>
      <guid>https://dev.to/jafaronpayments/how-to-verify-payment-webhooks-in-nodejs-so-you-never-ship-an-order-twice-2io9</guid>
      <description>&lt;p&gt;A customer pays for their order. A second later, your server gets the same "payment completed" message twice, and your system happily sends two parcels. Or worse, someone sends your server a fake "paid" message and walks away with a free order.&lt;/p&gt;

&lt;p&gt;If you build checkouts or payment features in Node.js, payment webhooks are where these bugs live. This guide covers the five things you need to get right, with code you can copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a payment webhook is
&lt;/h2&gt;

&lt;p&gt;A webhook is a message your payment provider sends to your server when something happens, like "this payment succeeded" or "this refund failed". Instead of your app asking "is it paid yet?" every few seconds, the provider tells you.&lt;/p&gt;

&lt;p&gt;That's handy, but it means your server has to answer three questions every time a message arrives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this message really from my payment provider?&lt;/li&gt;
&lt;li&gt;Have I already handled it?&lt;/li&gt;
&lt;li&gt;Did I save it before replying "got it"?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the same webhook can arrive twice
&lt;/h2&gt;

&lt;p&gt;Webhooks are usually delivered "at least once". If your server is slow, crashes or replies with an error, the provider sends the same message again, sometimes for many hours. The open Standard Webhooks specification, which many API companies follow, expects this and recommends giving every message a unique ID so you can spot repeats.&lt;/p&gt;

&lt;p&gt;So duplicates aren't a rare edge case. They're normal, and your code has to expect them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Keep the raw request body
&lt;/h2&gt;

&lt;p&gt;Most payment providers sign each webhook with HMAC-SHA256. That's a way of proving the message came from them and wasn't changed on the way. The signature is worked out from the exact bytes they sent.&lt;/p&gt;

&lt;p&gt;If you parse the JSON and turn it back into text, those bytes can change (spacing, key order) and the check fails. So read the raw body on your webhook route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBHOOK_SIGNING_SECRET&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;SIGNATURE_HEADER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// use the header name in your provider's docs&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/payments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="c1"&gt;// keep the exact bytes&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isValidSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SIGNATURE_HEADER&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isNew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveEventOnce&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="c1"&gt;// save first...&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;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="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                    &lt;span class="c1"&gt;// ...then reply&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNew&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="c1"&gt;// slow work last&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;h2&gt;
  
  
  Step 2: Check the signature safely
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;header&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^v1=/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// some providers add a version prefix&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;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;rawBody&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;hex&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;Two details matter here. A normal &lt;code&gt;===&lt;/code&gt; comparison can leak tiny timing differences that an attacker can measure, so use &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt;. And &lt;code&gt;timingSafeEqual&lt;/code&gt; throws an error if the two values are different lengths, so check the length first.&lt;/p&gt;

&lt;p&gt;Providers format signatures slightly differently (hex or base64, with or without a version prefix), so match these lines to your provider's &lt;a href="https://docs.paywithatoa.com/api-reference/webhook/introduction" rel="noopener noreferrer"&gt;webhook signature verification&lt;/a&gt; guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Save each event only once
&lt;/h2&gt;

&lt;p&gt;Handling repeats safely is called making your handler "idempotent": running it twice has the same effect as running it once. The simplest way is a unique key for every event and a database rule that rejects duplicates:&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;saveEventOnce&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="c1"&gt;// Use the event ID if your provider sends one&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&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="o"&gt;??&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;type&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;paymentId&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;status&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhookEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// unique index on "key"&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="nx"&gt;err&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNIQUE_VIOLATION&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// seen it before&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&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;h2&gt;
  
  
  Step 4: Reply fast, then do the slow work
&lt;/h2&gt;

&lt;p&gt;Providers only wait a short time for your reply. If you send emails, update stock or call other services first, the provider may give up and send the message again. This matters most at an &lt;a href="https://paywithatoa.com/gb/products/online-checkouts" rel="noopener noreferrer"&gt;online checkout&lt;/a&gt;, where a customer is staring at the confirmation page. Save the event, reply with 200, then do the slow work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Test it before real money moves
&lt;/h2&gt;

&lt;p&gt;Use your provider's sandbox (a test version that uses fake money) to send test webhooks. Try three cases: a normal payment, the same message twice, and a message with a broken signature. Your code should handle the first, ignore the second and reject the third.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist before you go live
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The webhook route reads the raw body&lt;/li&gt;
&lt;li&gt;Signatures are checked with a constant-time comparison&lt;/li&gt;
&lt;li&gt;Every event is saved once, using a unique key&lt;/li&gt;
&lt;li&gt;You reply 200 only after the event is saved&lt;/li&gt;
&lt;li&gt;Duplicates and bad signatures are tested in the sandbox&lt;/li&gt;
&lt;li&gt;A back-up job checks the status of any payment you haven't heard about&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Webhooks are how your payment provider tells your app that money has moved, and most providers send them "at least once", so the same message can arrive twice. Keep the raw body, check the signature, save each event once and reply fast, and a retry will never turn into a second order. Run the checklist above in your sandbox before you switch on live payments.&lt;/p&gt;

</description>
      <category>node</category>
      <category>webdev</category>
      <category>security</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Get Paid on Time: Practical Advice for Consultants and Agencies</title>
      <dc:creator>Jafar on Payments</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:13:21 +0000</pubDate>
      <link>https://dev.to/jafaronpayments/how-to-get-paid-on-time-practical-advice-for-consultants-and-agencies-j6c</link>
      <guid>https://dev.to/jafaronpayments/how-to-get-paid-on-time-practical-advice-for-consultants-and-agencies-j6c</guid>
      <description>&lt;p&gt;Finishing a project does not always mean getting paid for it. An agency may deliver on schedule and receive the client’s approval, yet wait weeks for the invoice to clear. For consultants and agencies with salaries, freelancers, software subscriptions and tax bills to cover, even a small delay can put pressure on cash flow.&lt;/p&gt;

&lt;p&gt;Learning how to get paid on time is not simply about sending more reminders. Payment needs to be considered when the client is assessed, the contract is written and the project is divided into stages. The invoice itself is only one part of that process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Clients Pay Invoices Late
&lt;/h2&gt;

&lt;p&gt;Late payment does not always indicate that a client is unwilling to pay. An invoice can be delayed because it was sent to the project manager instead of accounts payable, omitted a purchase-order number or arrived after the cut-off for the next payment run.&lt;/p&gt;

&lt;p&gt;There may also be a disagreement about whether a milestone has been completed. In other cases, the client intends to pay but puts it off because the payment process requires several manual steps.&lt;/p&gt;

&lt;p&gt;Consider an agency that completes the first stage of a website project on Friday. It issues the invoice the following Wednesday and sends it to the marketing manager. The manager forwards it to finance several days later, but finance rejects it because there is no purchase-order number.&lt;/p&gt;

&lt;p&gt;By the time the corrected invoice reaches the next payment run, almost three weeks have passed. The problem did not begin when the client missed a deadline. It began when the agency failed to understand the client’s payment process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the Client Before Starting
&lt;/h2&gt;

&lt;p&gt;Consultants and agencies usually assess whether they can deliver the requested work. They should also consider whether the client is likely to pay for it.&lt;/p&gt;

&lt;p&gt;Confirm the client’s legal business name, billing address and company number where applicable. For a higher-value project, a basic search through &lt;a href="https://find-and-update.company-information.service.gov.uk/" rel="noopener noreferrer"&gt;Companies House&lt;/a&gt; can help confirm that the business is active and that the contract names the correct legal entity.&lt;/p&gt;

&lt;p&gt;Ask the client how new suppliers are approved. Larger organisations may require vendor onboarding, insurance documents, tax information, a purchase order or registration on an invoicing portal.&lt;/p&gt;

&lt;p&gt;It also helps to understand the payment timetable. If invoices are processed once a month, missing an internal approval deadline could add several weeks to the wait.&lt;/p&gt;

&lt;p&gt;Payment history should influence future terms. An established client that has consistently paid on time may justify greater flexibility than a new customer requesting a large amount of work on extended credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Clear Payment Terms
&lt;/h2&gt;

&lt;p&gt;Payment terms should be agreed before work begins, not introduced at the bottom of the first invoice.&lt;/p&gt;

&lt;p&gt;The contract should state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agreed price and whether VAT applies&lt;/li&gt;
&lt;li&gt;When invoices will be issued&lt;/li&gt;
&lt;li&gt;The number of days allowed for payment&lt;/li&gt;
&lt;li&gt;The payment methods available&lt;/li&gt;
&lt;li&gt;How additional work will be approved and charged&lt;/li&gt;
&lt;li&gt;How disputed invoices will be handled&lt;/li&gt;
&lt;li&gt;Whether work may be paused when payment is overdue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use an exact payment period. “Payment is due within 14 calendar days of the invoice date” is clearer than “payment due promptly.” Each invoice should also show a specific due date.&lt;/p&gt;

&lt;p&gt;Consultants sometimes offer 30- or 60-day terms automatically because they assume larger clients will not accept anything else. However, long payment terms can leave the supplier financing the project. If an agency must pay employees and contractors before receiving the client’s money, the terms should reflect that risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request an Upfront Deposit
&lt;/h2&gt;

&lt;p&gt;A deposit is useful when a project requires substantial work or expenditure before the first deliverable is completed. It confirms the client’s commitment and provides funds for initial costs such as research, software, travel or freelance support.&lt;/p&gt;

&lt;p&gt;There is no standard deposit that suits every project. Some consultants request 25% upfront, while agencies working on shorter projects may ask for 50%. Another option is to charge for the first phase in full before work starts.&lt;/p&gt;

&lt;p&gt;The contract should explain when the deposit is due, whether it is refundable and what happens if the client cancels or postpones the project. If payment is a condition of reserving time, avoid beginning substantial work before the deposit has cleared.&lt;/p&gt;

&lt;p&gt;A deposit should not be presented as a sign of distrust. It is a practical way for both parties to share the financial commitment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Milestone Payments
&lt;/h2&gt;

&lt;p&gt;Billing only at the end of a long project places most of the financial risk on the consultant or agency. A four-month engagement invoiced after final delivery effectively requires the supplier to fund the work until completion.&lt;/p&gt;

&lt;p&gt;Milestone payments spread that risk more evenly. A website project might be invoiced at the following points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the project is booked&lt;/li&gt;
&lt;li&gt;After the strategy is approved&lt;/li&gt;
&lt;li&gt;When the first working version is delivered&lt;/li&gt;
&lt;li&gt;Following final handover&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each milestone should describe an event that both sides can recognise. “Project 75% complete” is open to interpretation. “Staging website submitted for client review” is easier to verify.&lt;/p&gt;

&lt;p&gt;For ongoing work, a monthly retainer may provide steadier cash flow. The agreement should specify whether the retainer is paid in advance or in arrears, what it includes and how work outside the agreed allowance will be charged.&lt;/p&gt;

&lt;p&gt;Milestone billing also provides regular opportunities to identify dissatisfaction or approval problems. It is better to find out that a client has concerns after the first stage than after the entire project has been delivered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Invoice Payments Easy
&lt;/h2&gt;

&lt;p&gt;An accurate invoice can still be inconvenient to pay. The client may need to locate the supplier’s bank details, create a new payee, enter the amount and type the correct reference. If the invoice arrives while the recipient is travelling or between meetings, the task may be postponed.&lt;/p&gt;

&lt;p&gt;Different clients prefer different methods. Some use bank transfers, while others prefer cards or links that take them directly to a payment screen. Providing appropriate &lt;a href="https://paywithatoa.co.uk/invoice-payments/" rel="noopener noreferrer"&gt;invoice payment options&lt;/a&gt; can reduce manual steps and make it easier to include the correct amount and reference.&lt;/p&gt;

&lt;p&gt;That does not mean offering every available payment method. Consultants and agencies should consider transaction values, processing fees, settlement times, client preferences and how payments will be matched with invoices in their accounting software.&lt;/p&gt;

&lt;p&gt;The practical test is simple: once the invoice has been approved, how much effort does the client need to make before it is paid? If they must search old emails or request bank details again, the process contains unnecessary friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send Accurate Invoices Promptly
&lt;/h2&gt;

&lt;p&gt;Waiting several days to send an invoice delays the date on which payment can begin. Unless the contract specifies a fixed monthly billing cycle, issue the invoice as soon as the deposit, milestone or service period becomes chargeable.&lt;/p&gt;

&lt;p&gt;Consistency matters for retainers. Sending the invoice on the same day each month helps the client fit it into an established approval routine.&lt;/p&gt;

&lt;p&gt;The description should be specific enough for someone outside the project team to understand. “Consulting services” tells an accounts-payable employee very little. “Customer research and competitor analysis completed in September 2026” is easier to connect with an approved project.&lt;/p&gt;

&lt;p&gt;A UK invoice should generally include a unique invoice number, supplier and customer details, the invoice date, the supply date, a description of the work, the amount owed and the payment terms. VAT invoices and invoices from particular business structures require additional information. The &lt;a href="https://www.gov.uk/invoicing-and-taking-payment-from-customers/invoices-what-they-must-include" rel="noopener noreferrer"&gt;GOV.UK invoicing guidance&lt;/a&gt; explains the current requirements.&lt;/p&gt;

&lt;p&gt;Before sending an invoice, check the:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client’s legal name and billing address&lt;/li&gt;
&lt;li&gt;Invoice number and date&lt;/li&gt;
&lt;li&gt;Description of the work&lt;/li&gt;
&lt;li&gt;Purchase-order number&lt;/li&gt;
&lt;li&gt;VAT treatment&lt;/li&gt;
&lt;li&gt;Total amount&lt;/li&gt;
&lt;li&gt;Payment deadline&lt;/li&gt;
&lt;li&gt;Payment instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A brief review before sending can prevent a much longer delay later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the Right Payment Contact
&lt;/h2&gt;

&lt;p&gt;The person who commissioned the work may approve the invoice, but that does not mean they control the payment.&lt;/p&gt;

&lt;p&gt;Ask for the accounts-payable contact’s details at the beginning of the engagement. Find out whether invoices must be submitted to a particular email address or uploaded to a finance portal.&lt;/p&gt;

&lt;p&gt;Where appropriate, send the invoice directly to accounts payable and copy the project contact. Include any required timesheets, delivery confirmations or purchase-order documents with the first submission.&lt;/p&gt;

&lt;p&gt;It is also worth asking when the organisation runs supplier payments and when an approved invoice must be received. Missing a weekly cut-off by one day could delay payment for another week.&lt;/p&gt;

&lt;p&gt;These details may feel minor compared with the work itself, but dependable cash flow is usually built on simple processes performed consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevent Invoice Disputes
&lt;/h2&gt;

&lt;p&gt;A client may question an invoice because the amount differs from the contract, a milestone has not been accepted or additional work was not authorised. These concerns should be investigated rather than treated as ordinary late payment.&lt;/p&gt;

&lt;p&gt;Many disputes can be prevented by recording decisions in writing. When the client requests work outside the original scope, explain the cost and obtain approval before proceeding. When a milestone is delivered, ask the client to confirm that it has been accepted.&lt;/p&gt;

&lt;p&gt;The contract should explain how invoice queries will be managed. Clients should raise concerns promptly and identify the amount being disputed. A question about one charge should not automatically prevent discussion about the undisputed balance.&lt;/p&gt;

&lt;p&gt;Clear records protect both sides. They show the client how the amount was calculated and prevent a vague disagreement from becoming an indefinite delay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Reminder Schedule
&lt;/h2&gt;

&lt;p&gt;Do not wait until an invoice is several weeks overdue before contacting the client. A short reminder before the deadline may uncover a missing approval while there is still time to correct it.&lt;/p&gt;

&lt;p&gt;A practical sequence could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three days before the deadline: Confirm that the invoice has been received&lt;/li&gt;
&lt;li&gt;On the due date: Remind the client that payment is due&lt;/li&gt;
&lt;li&gt;One to three days overdue: Request an expected payment date&lt;/li&gt;
&lt;li&gt;Seven days overdue: Contact accounts payable directly&lt;/li&gt;
&lt;li&gt;Fourteen days overdue: Escalate to the project owner or senior contact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each reminder should include the invoice number, amount, original due date and payment instructions. Keep the language factual rather than accusatory.&lt;/p&gt;

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

&lt;p&gt;I’m checking that Invoice 123 for £2,400 is scheduled for payment on 15 September. Please let me know if your finance team needs any further information from us.&lt;/p&gt;

&lt;p&gt;Accounting software can automate routine reminders, but replies should still be monitored. If the client raises a genuine query, pause the automated sequence and address it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call When Email Stops Working
&lt;/h2&gt;

&lt;p&gt;Another email may not help if several previous messages have been ignored. Call the accounts-payable contact and ask whether the invoice has been received, approved and entered into the next payment run.&lt;/p&gt;

&lt;p&gt;The purpose of the call is to identify the obstacle. The invoice may be awaiting approval, missing a document or sitting in the wrong system.&lt;/p&gt;

&lt;p&gt;If finance cannot resolve the issue, involve the person who commissioned the work. Explain what is outstanding, which reminders have been sent and what information you need.&lt;/p&gt;

&lt;p&gt;Keep a record of calls, emails and promised payment dates. This history will be useful if the matter later needs to be escalated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pause Work When Necessary
&lt;/h2&gt;

&lt;p&gt;Continuing to deliver while an invoice remains unpaid increases the amount at risk. It may also signal that the agreed payment terms are optional.&lt;/p&gt;

&lt;p&gt;Where the contract allows it, consider pausing the next phase when an undisputed invoice becomes seriously overdue. Give reasonable notice and explain the position calmly.&lt;/p&gt;

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

&lt;p&gt;Invoice 123 remains outstanding despite our previous reminders. We will need to pause the next stage of work until payment has been received. Please let us know if anything is preventing the invoice from being processed.&lt;/p&gt;

&lt;p&gt;Pausing work should be a standard commercial boundary, not a threat made in frustration. Apply the policy consistently.&lt;/p&gt;

&lt;p&gt;Repeated late payment should also affect future arrangements. A client that regularly exceeds its terms may need to pay a larger deposit, accept shorter terms or settle the full amount before further work begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand UK Late Payment Rules
&lt;/h2&gt;

&lt;p&gt;For qualifying business-to-business debts, UK suppliers may be entitled to charge statutory interest at 8% above the Bank of England base rate. If the contract already specifies a different interest arrangement, the contractual provisions may apply instead.&lt;/p&gt;

&lt;p&gt;Businesses may also be entitled to fixed debt-recovery compensation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;£40 for debts below £1,000&lt;/li&gt;
&lt;li&gt;£70 for debts from £1,000 to £9,999.99&lt;/li&gt;
&lt;li&gt;£100 for debts of £10,000 or more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://www.gov.uk/late-commercial-payments-interest-debt-recovery" rel="noopener noreferrer"&gt;GOV.UK late commercial payment guidance&lt;/a&gt; explains when a payment becomes late and how interest and recovery costs may be claimed.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.smallbusinesscommissioner.gov.uk/faqs/" rel="noopener noreferrer"&gt;Small Business Commissioner&lt;/a&gt; also provides guidance on payment terms and disputes involving small suppliers and larger private-sector businesses.&lt;/p&gt;

&lt;p&gt;These rights can be useful when an accepted invoice remains unpaid, but they should be applied carefully. A genuinely disputed invoice, an insolvent client or an international contract may require a different approach. Consider obtaining independent legal advice when the debt is significant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review Your Payment Process
&lt;/h2&gt;

&lt;p&gt;One late invoice may be an isolated mistake. Repeated delays usually indicate a wider problem with the client or the payment process.&lt;/p&gt;

&lt;p&gt;Monitor how long it takes to issue an invoice after completing work, the percentage paid by the due date and the clients that regularly exceed their terms. Record why invoices are rejected and how long disputes take to resolve.&lt;/p&gt;

&lt;p&gt;These patterns can reveal where changes are needed. Frequent missing purchase orders suggest a weakness in client onboarding. Delayed invoicing points to an internal process problem. Payments without recognisable references may mean the available payment instructions need to be improved.&lt;/p&gt;

&lt;p&gt;The objective is not to become more persistent at chasing invoices. It is to reduce how often chasing is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Paid on Time Consistently
&lt;/h2&gt;

&lt;p&gt;Late payments cannot always be prevented. A client may experience financial difficulties, an invoice may be genuinely disputed or an internal approval may take longer than expected.&lt;/p&gt;

&lt;p&gt;However, consultants and agencies can remove many avoidable delays by checking clients carefully, agreeing clear payment terms and invoicing throughout the project. Accurate invoices, convenient payment methods and consistent reminders make the remaining process easier to manage.&lt;/p&gt;

&lt;p&gt;Getting paid on time should not depend on remembering to send an uncomfortable email. It should be the expected result of a payment process that both the supplier and the client understand from the beginning.&lt;/p&gt;

</description>
      <category>latepayments</category>
      <category>invoicemanagement</category>
      <category>cashflowforconsultancies</category>
    </item>
  </channel>
</rss>
