We are having webhook duplicates logged on square side too, can you fix it?

We have been using webhooks for a while but some times we get duplicates, kind of once every day and it’s logged on the Square webhooks logs with difference of miliseconds, can you fix it?

Here is an example of the duplicates ids:

Webhook 1 id: BTQFsU7kwgJCu00974Dz12QyCSjlahgjJHnHUv5mvMI
Webhook 2 id: hCvDesLdBpmQ/LRgQprfSbz6MByJ1xTAd7KdT4iXkVc=

Log 1:

Log 2:
200 order.fulfillment.updated https://www.zohoapis.com/crm/v2/functions/square_orders_to_crm/actions/execute?auth_type=apikey&zapikey=1003.30a61740e29a7190815281fec2f4af87.14edc5cc52b9e46664384e288b2b724a 2023-11-16 23:24:40 GMT-5

Webhooks may be sent more than once. You’ll need to respond with a 2xx HTTP status code to Square as soon as possible to acknowledge the receipt of the event notification. If your application fails to acknowledge in a timely manner, a duplicated event will be sent that your application has 10 seconds to respond to. :slightly_smiling_face:

Hi @Bryan-Square, we are sending the timely answer 100% of the times. And as you can see in the example I sent you the repeated webhook was triggered exactly exactly at the same time without waiting any seconds, reason why it’s an issue on your side. You can verify this by checking on each id:

Webhook 1 id: BTQFsU7kwgJCu00974Dz12QyCSjlahgjJHnHUv5mvMI
Triggered at: 2023-11-16 23:24:36 GMT-5
Sent at: 2023-11-16 23:24:39 GMT-5

Webhook 2 id: hCvDesLdBpmQ/LRgQprfSbz6MByJ1xTAd7KdT4iXkVc=
Triggered at: 2023-11-16 23:24:36 GMT-5
Sent at: 2023-11-16 23:24:40 GMT-5

Now it’s seems happening more than once every day, can you fix it?

Those aren’t duplicate events. The first event for BTQFsU7kwgJCu00974Dz12QyCSjlahgjJHnHUv5mvMI is for this payload:

{
  "merchant_id": "MLB0M4PXGJ7GV",
  "type": "order.fulfillment.updated",
  "event_id": "6527f5b9-8cf9-33d0-841a-22be76a0bb59",
  "created_at": "2023-11-17T04:24:33Z",
  "data": {
    "type": "order",
    "id": "tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
    "object": {
      "order_fulfillment_updated": {
        "created_at": "2023-11-17T04:24:32.844Z",
        "fulfillment_update": [
          {
            "fulfillment_uid": "vyysEynWxh3FS4dn4ai8cD",
            "new_state": "PROPOSED"
          }
        ],
        "location_id": "LXE4FC194XMVZ",
        "order_id": "tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
        "state": "OPEN",
        "updated_at": "2023-11-17T04:24:32.844Z",
        "version": 1
      }
    }
  }
}

The second event Webhook 2 id: hCvDesLdBpmQ/LRgQprfSbz6MByJ1xTAd7KdT4iXkVc
is for this payload:

{
  "merchant_id": "MLB0M4PXGJ7GV",
  "type": "order.fulfillment.updated",
  "event_id": "034cc0e0-b900-3716-8ba1-0674c964aa62",
  "created_at": "2023-11-17T04:24:34Z",
  "data": {
    "type": "order",
    "id": "tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
    "object": {
      "order_fulfillment_updated": {
        "created_at": "2023-11-17T04:24:32.844Z",
        "fulfillment_update": [
          {
            "fulfillment_uid": "vyysEynWxh3FS4dn4ai8cD",
            "new_state": "PROPOSED",
            "old_state": "PROPOSED"
          }
        ],
        "location_id": "LXE4FC194XMVZ",
        "order_id": "tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
        "state": "OPEN",
        "updated_at": "2023-11-17T04:24:34.009Z",
        "version": 3
      }
    }
  }
}

The first is for version 1 of the order and the second is version 3. There may not be a difference in the order webhook payload since the webhook doesn’t return the full order object. However if you RetrieveOrder when you get the event you’ll see a difference in the full payload of the order returned by the API. :slightly_smiling_face:

But @Bryan-Square, it’s none sense, we monitor webhooks for create, updates and fulfillment_update to keep in our CRM whatever our customers do in Square. Still, every time an order is created in Square we get 1 created webhook, 3 updates and 3 fulfillment_updates webhooks, all at the same time and not logical order, usually we get an update and filfillment_update webhooks before the created webhook. And it seems the versions has nothing to do with the Customer actions, how can there be all those verions 1, 2, 3, etc when 1 order is created if the customer did not more actions? Why versions without the customer doing changes? all payloads are the same. Every time we get any webhook the CRM pulls the order payload immediately but there are no changes. Next I’m including the payloads we got on each of these webhooks and using a code compare plugin shows both matching:

For BTQFsU7kwgJCu00974Dz12QyCSjlahgjJHnHUv5mvMI this is the order payload pulled at that time:

{
"order":{
"id":"tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
"location_id":"LXE4FC194XMVZ",
"line_items":[
{
"uid":"NX4ZN19in1pAot0fvMmO8",
"catalog_object_id":"XOZHBXAROPXGWEI3YBRPWHZ4",
"catalog_version":1700174136440,
"quantity":"1",
"name":"Cherry Pie",
"variation_name":"9 inch",
"base_price_money":{
"amount":2600,
"currency":"USD"
},
"gross_sales_money":{
"amount":2600,
"currency":"USD"
},
"total_tax_money":{
"amount":0,
"currency":"USD"
},
"total_discount_money":{
"amount":260,
"currency":"USD"
},
"total_money":{
"amount":2340,
"currency":"USD"
},
"variation_total_price_money":{
"amount":2600,
"currency":"USD"
},
"applied_discounts":[
{
"uid":"mDkEL2vlV3clDY2LbEZlrC",
"discount_uid":"6556eb00b8a66",
"applied_money":{
"amount":260,
"currency":"USD"
}
}
],
"item_type":"ITEM",
"total_service_charge_money":{
"amount":0,
"currency":"USD"
}
}
],
"discounts":[
{
"uid":"6556eb00b8a66",
"name":"Discount: Coupon PIES23",
"amount_money":{
"amount":260,
"currency":"USD"
},
"applied_money":{
"amount":260,
"currency":"USD"
},
"type":"FIXED_AMOUNT",
"scope":"LINE_ITEM"
}
],
"fulfillments":[
{
"uid":"vyysEynWxh3FS4dn4ai8cD",
"type":"PICKUP",
"state":"PROPOSED",
"pickup_details":{
"pickup_at":"2023-11-23T20:00:00.000Z",
"note":"",
"placed_at":"2023-11-17T04:24:34.008Z",
"schedule_type":"SCHEDULED",
"recipient":{
"display_name":"Marilynn Tham",
"email_address":"[email protected]",
"phone_number":"+14156998542",
"address":{
"address_line_1":"1820 Solano Ave",
"locality":"Berkeley",
"administrative_district_level_1":"CA",
"postal_code":"94707",
"country":"US",
"organization":"Lavender Bakery & Cafe'"
}
},
"prep_time_duration":"PT0S"
},
"line_item_application":"ALL"
}
],
"created_at":"2023-11-17T04:24:32.844Z",
"updated_at":"2023-11-17T04:24:34.009Z",
"state":"OPEN",
"version":3,
"reference_id":"11ee850054142be8b9beac1f6bbba828",
"total_tax_money":{
"amount":0,
"currency":"USD"
},
"total_discount_money":{
"amount":260,
"currency":"USD"
},
"total_tip_money":{
"amount":260,
"currency":"USD"
},
"total_money":{
"amount":2600,
"currency":"USD"
},
"tenders":[
{
"id":"B4W9AgwsF4jDDYNkJOQwBVgzH98YY",
"location_id":"LXE4FC194XMVZ",
"transaction_id":"tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
"created_at":"2023-11-17T04:24:33.458Z",
"amount_money":{
"amount":2600,
"currency":"USD"
},
"type":"CARD",
"card_details":{
"status":"CAPTURED",
"card":{
"card_brand":"VISA",
"last_4":"1606",
"exp_month":12,
"exp_year":2028,
"fingerprint":"sq-1-SKib6tYcQ16f1ga3-pEj4ARSoAUjgWdD_mM6228DFSKdYQ1B8Q21PPA3i5iVzw0cdw",
"card_type":"CREDIT",
"prepaid_type":"NOT_PREPAID",
"bin":"438857"
},
"entry_method":"ON_FILE"
},
"tip_money":{
"amount":260,
"currency":"USD"
},
"payment_id":"B4W9AgwsF4jDDYNkJOQwBVgzH98YY"
}
],
"total_service_charge_money":{
"amount":0,
"currency":"USD"
},
"net_amounts":{
"total_money":{
"amount":2600,
"currency":"USD"
},
"tax_money":{
"amount":0,
"currency":"USD"
},
"discount_money":{
"amount":260,
"currency":"USD"
},
"tip_money":{
"amount":260,
"currency":"USD"
},
"service_charge_money":{
"amount":0,
"currency":"USD"
}
},
"source":{
"name":"Square Online"
},
"net_amount_due_money":{
"amount":0,
"currency":"USD"
}
}
}

For hCvDesLdBpmQ/LRgQprfSbz6MByJ1xTAd7KdT4iXkVc this is the order payload pulled at that time:

{
"order":{
"id":"tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
"location_id":"LXE4FC194XMVZ",
"line_items":[
{
"uid":"NX4ZN19in1pAot0fvMmO8",
"catalog_object_id":"XOZHBXAROPXGWEI3YBRPWHZ4",
"catalog_version":1700174136440,
"quantity":"1",
"name":"Cherry Pie",
"variation_name":"9 inch",
"base_price_money":{
"amount":2600,
"currency":"USD"
},
"gross_sales_money":{
"amount":2600,
"currency":"USD"
},
"total_tax_money":{
"amount":0,
"currency":"USD"
},
"total_discount_money":{
"amount":260,
"currency":"USD"
},
"total_money":{
"amount":2340,
"currency":"USD"
},
"variation_total_price_money":{
"amount":2600,
"currency":"USD"
},
"applied_discounts":[
{
"uid":"mDkEL2vlV3clDY2LbEZlrC",
"discount_uid":"6556eb00b8a66",
"applied_money":{
"amount":260,
"currency":"USD"
}
}
],
"item_type":"ITEM",
"total_service_charge_money":{
"amount":0,
"currency":"USD"
}
}
],
"discounts":[
{
"uid":"6556eb00b8a66",
"name":"Discount: Coupon PIES23",
"amount_money":{
"amount":260,
"currency":"USD"
},
"applied_money":{
"amount":260,
"currency":"USD"
},
"type":"FIXED_AMOUNT",
"scope":"LINE_ITEM"
}
],
"fulfillments":[
{
"uid":"vyysEynWxh3FS4dn4ai8cD",
"type":"PICKUP",
"state":"PROPOSED",
"pickup_details":{
"pickup_at":"2023-11-23T20:00:00.000Z",
"note":"",
"placed_at":"2023-11-17T04:24:34.008Z",
"schedule_type":"SCHEDULED",
"recipient":{
"display_name":"Marilynn Tham",
"email_address":"[email protected]",
"phone_number":"+14156998542",
"address":{
"address_line_1":"1820 Solano Ave",
"locality":"Berkeley",
"administrative_district_level_1":"CA",
"postal_code":"94707",
"country":"US",
"organization":"Lavender Bakery & Cafe'"
}
},
"prep_time_duration":"PT0S"
},
"line_item_application":"ALL"
}
],
"created_at":"2023-11-17T04:24:32.844Z",
"updated_at":"2023-11-17T04:24:34.009Z",
"state":"OPEN",
"version":3,
"reference_id":"11ee850054142be8b9beac1f6bbba828",
"total_tax_money":{
"amount":0,
"currency":"USD"
},
"total_discount_money":{
"amount":260,
"currency":"USD"
},
"total_tip_money":{
"amount":260,
"currency":"USD"
},
"total_money":{
"amount":2600,
"currency":"USD"
},
"tenders":[
{
"id":"B4W9AgwsF4jDDYNkJOQwBVgzH98YY",
"location_id":"LXE4FC194XMVZ",
"transaction_id":"tNGQNRAPcPGgq73JHq9PTDg6yvFZY",
"created_at":"2023-11-17T04:24:33.458Z",
"amount_money":{
"amount":2600,
"currency":"USD"
},
"type":"CARD",
"card_details":{
"status":"CAPTURED",
"card":{
"card_brand":"VISA",
"last_4":"1606",
"exp_month":12,
"exp_year":2028,
"fingerprint":"sq-1-SKib6tYcQ16f1ga3-pEj4ARSoAUjgWdD_mM6228DFSKdYQ1B8Q21PPA3i5iVzw0cdw",
"card_type":"CREDIT",
"prepaid_type":"NOT_PREPAID",
"bin":"438857"
},
"entry_method":"ON_FILE"
},
"tip_money":{
"amount":260,
"currency":"USD"
},
"payment_id":"B4W9AgwsF4jDDYNkJOQwBVgzH98YY"
}
],
"total_service_charge_money":{
"amount":0,
"currency":"USD"
},
"net_amounts":{
"total_money":{
"amount":2600,
"currency":"USD"
},
"tax_money":{
"amount":0,
"currency":"USD"
},
"discount_money":{
"amount":260,
"currency":"USD"
},
"tip_money":{
"amount":260,
"currency":"USD"
},
"service_charge_money":{
"amount":0,
"currency":"USD"
}
},
"source":{
"name":"Square Online"
},
"net_amount_due_money":{
"amount":0,
"currency":"USD"
}
}
}

This is the documented behavior with our webhook in the Requirements and limitations.

  • There’s no guarantee of the delivery order of event notices.

Also the returned order payload you provided for BTQFsU7kwgJCu00974Dz12QyCSjlahgjJHnHUv5mvMI is version 3 and if you would have gotten version 1 you would have seen the difference. Unfortunately, we don’t have the ability to return previous versions of the order at this time.

You can create a filter to look for changes only for the fields your application cares about and ignore the rest. :slightly_smiling_face:

But @Bryan-Square, do you see it? there is something wrong in your logic or how Square’s system works:

  1. Can you explain why the version 1 webhook was triggered at the same time as the version 3?
  2. How would you suggest we get the version 1 order payload, if the version 3 was triggered at the same time?
  3. And additionally, how can you explain that version 1 and 3 webhooks were triggered at the same time without version 2?

Webhook 1 id: BTQFsU7kwgJCu00974Dz12QyCSjlahgjJHnHUv5mvMI
Triggered at: 2023-11-16 23:24:36 GMT-5
Sent at: 2023-11-16 23:24:39 GMT-5

Webhook 2 id: hCvDesLdBpmQ/LRgQprfSbz6MByJ1xTAd7KdT4iXkVc=
Triggered at: 2023-11-16 23:24:36 GMT-5
Sent at: 2023-11-16 23:24:40 GMT-5

When you receive the first event, is the information you need in the payload of the webhook or do you need to call the Orders API to get the information you need from the full order payload.

Also we did send version 2 of that order. From you’re webhook logs it arrived in this event: f436059d-e28a-32f2-ae58-c43f221d4734. :slightly_smiling_face:

But @Bryan-Square, you are not addressing the issue, the purpose of the webhooks it to notify our CRM about changes in the Orders using the API. But this is impossible if Square is sending more than 10 webhooks notifications at the same time and even when the customer did no changes at all. We understand there are different event types and versions, and we are looking at it, but still, if they are received at the same time there is no way to react upon the ones before because there is no before if all are thrown away at the same time.

So, when you say "When… " it’s none sense because Square is triggering version 2 and 3 fulfillment.order.updated notifications at the same time as shown before.

fulfillment.order.updated Version 1 id: BTQFsU7kwgJCu00974Dz12QyCSjlahgjJHnHUv5mvMI
Triggered at: 2023-11-16 23:24:36 GMT-5
Sent at: 2023-11-16 23:24:39 GMT-5

fulfillment.order.updated Version 3 id: hCvDesLdBpmQ/LRgQprfSbz6MByJ1xTAd7KdT4iXkVc=
Triggered at: 2023-11-16 23:24:36 GMT-5
Sent at: 2023-11-16 23:24:40 GMT-5

By the way, the event id you sent f436059d-e28a-32f2-ae58-c43f221d4734 corresponds to a order.update notification and we got it twice, 1st for Version 2 and 2nd for Version 3 of the order.update. So, as you can see, there is no Version 2 for the order.fulfillment.updated event. And those 2 Versions for the order.update you mentioned where sent with less than 1 second difference, which clearly indicates it was not a customer change, none sense:

f436059d-e28a-32f2-ae58-c43f221d4734 Version 2 order.update:
{“event_id”:“f436059d-e28a-32f2-ae58-c43f221d4734”,“created_at”:“2023-11-17T04:24:34Z”,“version”:2}

f436059d-e28a-32f2-ae58-c43f221d4734 Version 3 order.update:
{“event_id”:“f436059d-e28a-32f2-ae58-c43f221d4734”,“created_at”:“2023-11-17T04:24:34Z”,“version”:3}

Just to be clear, this issue is about getting the fulfillment.order.updated duplicated, without the customer doing any changes, at the same time, which ends producing duplicates in our system, because there is no way for our CRM to create one before the other because both notifications arrive at the same time. We save the order.id as unique field precisely to avoid duplicates, if an order is created and the order.id is already in the system an error stops the duplicate, but because both fulfillment.order.updated events arrive at the same time there is no time for one to be created the technically both duplicates are created at the same time.

Can you stop Square from sending different order.fulfillment.updated notifications when there are no changes in the order? or at least add some time between the different versions for 3rd party systems to have time to act when 1 version arrives and recognize something happened before?

Just to make sure the issue is explained, no 3rd party system can react to different notifications if received at the same time, it’s none sense. There has to be some time between to allow it to find those sent before and then avoid duplicates, is it clear?

At this time with webhooks there isn’t any additional filtering that can be configured on the Square side to reduce the amount of webhooks sent for orders. What fields are you specifically looking for changes? :slightly_smiling_face:

Hi @Bryan-Square, it’s impossible to look for changes if more than 1 notifications are produced at the same time. I think your answers are based on notifications being sent one after the other, so we/any system can look for changes, but the issue is that you can’t look for changes when the notifications are triggered at the same time.

Let me explain with an example to see if we finally get to the root of the issue:

  • Let say you create an order #0342 for 1 chocolate (version 1).
  • 5 minutes later, you enter and add 1 donut (version 2).
  • and 3 minutes later you enter and change your address apt from #4 to #9 (version 3).

If you receive those 3 notifications 1 after the other, it would make sense because you know how the order was created only with 1 chocolate, then you know it was added 1 donut and then the address apt was changed to #9. So, the right order #0342 finally has 1 chocolate, 1 donut and address apt #9.

Now, suppose all 3 notifications at sent the same time, the receiving system gets 3 notifications as this:

  • Order #0342 created. It has 1 chocolate and address apt #4 (version 1).
  • Order #0342 updated. It has 1 chocolate, 1 donut and address apt #4 (version 2).
  • Order #0342 updated. It has 1 chocolate, 1 donut and address apt #9 (version 3).

The issue, as explained before many times, is that these 3 notifications are produced simultaneously, so, there is no time to create any order #0342 before the others, and all 3 duplicates are created exactly at the same time because order #0342 is not found in the system. So, when you say “look for chages” or “when you receceive the first event” or “you can create a filter to look for changes” it’s completely non sense because there is nothing in the system to compare to.

And an additional explanation that may be useful to understand the issue, the payload is “not” coming in the notification, so, the system needs to retrieve the order to get it’s payload, but guess what… all notifications for the different versions are sent “at the same time”, so the retrieved order payload is “the same”, do you get it? How can the different version’s payloads can be retrieved if all notifications are triggered at the same time?

And you could say, if all payloads are the same take the 1st one and ignore the others. But again we fall in the same “none sense” fact of thinking that the system can receive 1 notification before the other, so, again, as in the example, if all notifications are produced simultaneously, the receiving system has no time to create/save the 1st record to compare to the next ones, so when 3 notifications are received simultaneously and there is nothing yet in the system the only option for the system is to create 3 different orders for the same Square order. Again, because all notification come simultaneously and none came before the others.

Is it clear now?

@user4233, I do understand what your describing here about the sequence of webhooks arrival. However this is the expected and documented behavior for webhook events. Please see Requirements and Limitations section of our documentation.

Here are some general strategies you can consider:

  1. Concurrency and Parallel Processing:
  • Use asynchronous processing or a multi-threaded/multi-process approach to handle multiple events concurrently.
  • If you’re using a language or framework that supports asynchronous programming, such as Python with asyncio or Node.js, consider leveraging these capabilities.
  1. Queueing Systems:
  • Implement a queueing system to manage incoming events. As events arrive, they are placed in a queue, and separate worker processes or threads consume and process them.
  • Popular queueing systems include RabbitMQ, Apache Kafka, and AWS Simple Queue Service (SQS).
  1. Throttling:
  • Implement a throttling mechanism to control the rate at which events are processed. This helps prevent overwhelming your system with a sudden influx of events.
  • Consider setting limits on the number of concurrent requests or events that can be processed simultaneously.
  1. Load Balancing:
  • Distribute incoming webhook requests across multiple servers or instances to balance the load.
  • Use a load balancer to evenly distribute traffic to different processing nodes.
  1. Idempotency:
  • Design your webhook handlers to be idempotent, meaning that processing the same event multiple times has the same result as processing it once. This helps handle duplicate events that may be sent due to retries or network issues.
  1. Error Handling and Retry Mechanism:
  • Implement a robust error handling mechanism to deal with failures during event processing.
  • Use exponential backoff and retries for failed or timed-out requests to avoid overloading the system during transient issues.
  1. Monitoring and Logging:
  • Implement logging and monitoring to keep track of the processing status and performance of your webhook handlers.
  • Set up alerts for abnormal behavior, errors, or performance issues.
  1. Scaling Infrastructure:
  • Be prepared to scale your infrastructure horizontally as the volume of incoming events increases.
  • Utilize cloud services that can automatically scale based on demand, such as AWS Auto Scaling or Google Cloud’s managed instance groups.
  1. Testing and Simulation:
  • Simulate high traffic scenarios during testing to ensure that your system can handle peak loads.
  • Perform load testing to identify bottlenecks and optimize your system accordingly.
    :slightly_smiling_face:

@Bryan-Square are you kidding? this is none sense at all, Square is a small business solution and it should offer an API easy and simple to integrate. We are currently receiving 4 versions on the fulfillment_update webhook in addition to the update and create webhooks when 1 online order is created. It means around 10 webhook notifications at the same time when 1 webhook for each type, 1 create and 1 fulfillment_update are enough, maybe 1 update if the fulfillment updates the order, but 10 notifications with 3 and 4 versions of the same event is completely none sense.

Having to put together a queuing system, throttling system, load balancing system just to be able to handle the high amount of none required notifications sent by Square? for small businesses? is completely none sense.

Hopefully, Square developers recognize soon that having this complicated set of APIs is it’s biggest weakness and move to a simpler API that allows them to improve at a faster rate. There are many limitations when you want to integrate Square with 3rd party apps and it seems having an overcomplicated, tangled and intricated set of APIs is the reason, making it very hard to work with and taking you years to release basic features and improvements, completely none sense.

@user4233 This is normal behavior for any webhook feed you subscribe to. It is normal for multiple webhooks to arrive at the same time, especially in scenarios where multiple events or updates trigger the webhook simultaneously. Webhooks are typically used to notify external systems or applications about events that occur in real-time. If multiple events occur concurrently or in quick succession, the corresponding webhooks may be sent simultaneously.

It’s important for the receiving end of the webhook to be able to handle and process multiple incoming requests concurrently. This often involves implementing appropriate concurrency and threading mechanisms in the receiving application or server to ensure that the processing of one webhook does not block the processing of others.

Additionally, the order in which webhooks arrive may not be guaranteed, so the receiving system should be designed to handle out-of-order delivery if necessary. Implementing proper error handling and retry mechanisms is also crucial to ensure that webhook notifications are reliably processed, even in the face of occasional failures or network issues. :slightly_smiling_face:

I’m facing the same issue and it adversely affects our system records, isn’t there a way Square can at least add a reasonable interval and not some milliseconds, would appreciate it!

Hi @Bryan-Square, many people is complaining about this issue because Square seems the only system sending webhooks like that. You say ‘This is normal behavior’ because you may have never seen other system handling webhooks in a different way that makes sense, but as you can see many users complaining about it, believe me that Square is doing it wrong, or, it could be better and do not send duplicated events at the same time.