laravel/cashier-paddle

Possible webhook race condition

Open

#310 建立於 2026年3月17日

在 GitHub 查看
 (1 留言) (0 反應) (0 負責人)PHP (71 fork)github user discovery
help wanted

倉庫指標

Star
 (268 star)
PR 合併指標
 (平均合併 15小時 44分鐘) (30 天內合併 1 個 PR)

描述

Cashier Paddle Version

2.7.0

Laravel Version

12

PHP Version

8.4

Database Driver & Version

MySQL 8.0.19-10

Description

I am seeing a periodic failure in our sandbox environment whereby our transactions table is listing completed transactions with a status of paid and a null invoice_number despite the transaction completing successfully (and an invoice number being generated).

I think this may be caused by a race condition whereby transaction.updated overwrites the status provided by transaction.completed and have attached three notification logs which seem to support this claim.

In the example provided Paddle sends three webhook notifications in quick succession for the same transaction:

  1. transaction.updated (status: paid) — occurred_at: 2026-03-16T15:49:08.670618Z
  2. transaction.updated (status: completed) — occurred_at: 2026-03-16T15:49:10.220417Z
  3. transaction.completed (status: completed) — occurred_at: 2026-03-16T15:49:10.222274Z

I believe the following race condition is occurring:

  • Notification 3 (transaction.completed) creates the local transaction record with status completed
  • Notification 1 (transaction.updated, status paid) arrives at the handler after the record has been created, and overwrites the status back to paid

The result is a transaction stuck at paid status despite transaction.completed having been received and acknowledged with a 200 response. Replaying notification 2 (transaction.updated, status completed) via the Paddle dashboard corrects the status, confirming the issue is delivery/processing order dependent.

We have a custom listener hooked up via EventListen which successfully fired upon receipt of the transaction.completed notification, verifying that the notification arrived and was processed correctly:

Event::listen(WebhookHandled::class, PaddleHandledEventListener::class);

I'd be keen to hear if this is a configuration issue on our side or something else. Apologies if I've missed something in the documentation. Our code doesn't write or modify the transactions table in any way outside of Paddle Cashier.

Notification payloads for reference (relevant names and urls anonymised but identifiers otherwise left intact):

  1. transaction.updated (status: paid)
{
  "event_id": "evt_01kkvncdxypqgpm6j60b57kp3g",
  "event_type": "transaction.updated",
  "occurred_at": "2026-03-16T15:49:08.670618Z",
  "notification_id": "ntf_01kkvnce76ab69dxrvzw3gfmb8",
  "data": {
    "id": "txn_01kkvncb7svc6x9302fnmgn9ca",
    "items": [
      {
        "price": {
          "id": "pri_01kkh7y92t7qpfnejczb2qstz6",
          "name": "Subscription",
          "type": "standard",
          "status": "active",
          "quantity": { "maximum": 999999, "minimum": 1 },
          "tax_mode": "location",
          "created_at": "2026-03-12T14:41:49.14661Z",
          "product_id": "pro_01kkh7s8fhc8s21zdf7bhbvmd2",
          "unit_price": { "amount": "14000", "currency_code": "USD" },
          "updated_at": "2026-03-13T14:25:30.083513Z",
          "custom_data": null,
          "description": "Subscription - Monthly",
          "trial_period": null,
          "billing_cycle": { "interval": "day", "frequency": 1 },
          "unit_price_overrides": []
        },
        "quantity": 1,
        "proration": null
      }
    ],
    "origin": "subscription_recurring",
    "status": "paid",
    "details": {
      "totals": {
        "fee": null,
        "tax": "2333",
        "total": "14000",
        "credit": "0",
        "balance": "0",
        "discount": "0",
        "earnings": null,
        "subtotal": "11667",
        "grand_total": "14000",
        "currency_code": "USD",
        "grand_total_tax": "2333",
        "credit_to_balance": "0"
      },
      "line_items": [
        {
          "id": "txnitm_01kkvncb8pggz1wbrt90wmr9qf",
          "totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667" },
          "product": {
            "id": "pro_01kkh7s8fhc8s21zdf7bhbvmd2",
            "name": "Plan",
            "type": "standard",
            "status": "active",
            "image_url": null,
            "created_at": "2026-03-12T14:39:04.689Z",
            "updated_at": "2026-03-12T14:39:04.689Z",
            "custom_data": {
              "slug": "plan_2",
              "type": "plan",
              "credit_amount": "400000",
              "max_topup_credits_per_month": "1100000"
            },
            "description": null,
            "tax_category": "saas"
          },
          "price_id": "pri_01kkh7y92t7qpfnejczb2qstz6",
          "quantity": 1,
          "tax_rate": "0.2",
          "unit_totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667"
}
        }
      ],
      "payout_totals": null,
      "tax_rates_used": [
        {
          "totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667" },
          "tax_rate": "0.2"
        }
      ],
      "adjusted_totals": {
        "fee": null,
        "tax": "2333",
        "total": "14000",
        "earnings": null,
        "subtotal": "11667",
        "grand_total": "14000",
        "retained_fee": "0",
        "currency_code": "USD",
        "grand_total_tax": "2333"
      }
    },
    "checkout": {
      "url": "https://example.com/checkout?_ptxn=txn_01kkvncb7svc6x9302fnmgn9ca"
    },
    "payments": [
      {
        "amount": "14000",
        "status": "captured",
        "created_at": "2026-03-16T15:49:06.042973Z",
        "error_code": null,
        "captured_at": "2026-03-16T15:49:08.212287Z",
        "method_details": {
          "card": { "type": "visa", "last4": "5556", "expiry_year": 2027, "expiry_month": 12,
"cardholder_name": "Customer" },
          "type": "card",
          "south_korea_local_card": null
        },
        "payment_method_id": "paymtd_01kkky4jk2g3p7cqr058y5n6j2",
        "payment_attempt_id": "16efcbef-0d41-428e-a2c6-920d4d9f7836",
        "stored_payment_method_id": "13663bc1-ae48-4316-99bb-ea165236ef70"
      }
    ],
    "billed_at": "2026-03-16T15:49:05.913853Z",
    "address_id": "add_01kkkxzss20bmqjny4f96gqad7",
    "created_at": "2026-03-16T15:49:05.964293Z",
    "invoice_id": null,
    "revised_at": null,
    "updated_at": "2026-03-16T15:49:08.664472579Z",
    "business_id": null,
    "custom_data": { "subscription_type": "default" },
    "customer_id": "ctm_01kkkxwav252thrbh9wnt577jr",
    "discount_id": null,
    "receipt_data": null,
    "currency_code": "USD",
    "billing_period": {
      "ends_at": "2026-03-17T15:48:15.785146Z",
      "starts_at": "2026-03-16T15:48:15.785146Z"
    },
    "invoice_number": null,
    "billing_details": null,
    "collection_mode": "automatic",
    "subscription_id": "sub_01kkky4p16q20d5pvdz50ch6eh"
  }
}
  1. transaction.updated (status: completed)
{
  "event_id": "evt_01kkvncfectscx1hfy4xrs0q2t",
  "event_type": "transaction.updated",
  "occurred_at": "2026-03-16T15:49:10.220417Z",
  "notification_id": "ntf_01kkvncg12j72575afh9b61exx",
  "data": {
    "id": "txn_01kkvncb7svc6x9302fnmgn9ca",
    "items": [
      {
        "price": {
          "id": "pri_01kkh7y92t7qpfnejczb2qstz6",
          "name": "Subscription",
          "type": "standard",
          "status": "active",
          "quantity": { "maximum": 999999, "minimum": 1 },
          "tax_mode": "location",
          "created_at": "2026-03-12T14:41:49.14661Z",
          "product_id": "pro_01kkh7s8fhc8s21zdf7bhbvmd2",
          "unit_price": { "amount": "14000", "currency_code": "USD" },
          "updated_at": "2026-03-13T14:25:30.083513Z",
          "custom_data": null,
          "description": "Subscription - Monthly",
          "trial_period": null,
          "billing_cycle": { "interval": "day", "frequency": 1 },
          "unit_price_overrides": []
        },
        "quantity": 1,
        "proration": null
      }
    ],
    "origin": "subscription_recurring",
    "status": "completed",
    "details": {
      "totals": {
        "fee": "750",
        "tax": "2333",
        "total": "14000",
        "credit": "0",
        "balance": "0",
        "discount": "0",
        "earnings": "10917",
        "subtotal": "11667",
        "grand_total": "14000",
        "currency_code": "USD",
        "grand_total_tax": "2333",
        "credit_to_balance": "0"
      },
      "line_items": [
        {
          "id": "txnitm_01kkvncb8pggz1wbrt90wmr9qf",
          "totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667" },
          "product": {
            "id": "pro_01kkh7s8fhc8s21zdf7bhbvmd2",
            "name": "Plan",
            "type": "standard",
            "status": "active",
            "image_url": null,
            "created_at": "2026-03-12T14:39:04.689Z",
            "updated_at": "2026-03-12T14:39:04.689Z",
            "custom_data": {
              "slug": "plan_2",
              "type": "plan",
              "credit_amount": "400000",
              "max_topup_credits_per_month": "1100000"
            },
            "description": null,
            "tax_category": "saas"
          },
          "price_id": "pri_01kkh7y92t7qpfnejczb2qstz6",
          "quantity": 1,
          "tax_rate": "0.2",
          "unit_totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667"
}
        }
      ],
      "payout_totals": {
        "fee": "750",
        "tax": "2333",
        "total": "14000",
        "credit": "0",
        "balance": "0",
        "discount": "0",
        "earnings": "10917",
        "fee_rate": "0.05",
        "subtotal": "11667",
        "grand_total": "14000",
        "currency_code": "USD",
        "exchange_rate": "1",
        "grand_total_tax": "2333",
        "credit_to_balance": "0"
      },
      "tax_rates_used": [
        {
          "totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667" },
          "tax_rate": "0.2"
        }
      ],
      "adjusted_totals": {
        "fee": "750",
        "tax": "2333",
        "total": "14000",
        "earnings": "10917",
        "subtotal": "11667",
        "grand_total": "14000",
        "retained_fee": "0",
        "currency_code": "USD",
        "grand_total_tax": "2333"
      }
    },
    "checkout": {
      "url": "https://example.com/checkout?_ptxn=txn_01kkvncb7svc6x9302fnmgn9ca"
    },
    "payments": [
      {
        "amount": "14000",
        "status": "captured",
        "created_at": "2026-03-16T15:49:06.042973Z",
        "error_code": null,
        "captured_at": "2026-03-16T15:49:08.212287Z",
        "method_details": {
          "card": { "type": "visa", "last4": "5556", "expiry_year": 2027, "expiry_month": 12,
"cardholder_name": "Customer" },
          "type": "card",
          "south_korea_local_card": null
        },
        "payment_method_id": "paymtd_01kkky4jk2g3p7cqr058y5n6j2",
        "payment_attempt_id": "16efcbef-0d41-428e-a2c6-920d4d9f7836",
        "stored_payment_method_id": "13663bc1-ae48-4316-99bb-ea165236ef70"
      }
    ],
    "billed_at": "2026-03-16T15:49:05.913853Z",
    "address_id": "add_01kkkxzss20bmqjny4f96gqad7",
    "created_at": "2026-03-16T15:49:05.964293Z",
    "invoice_id": "inv_01kkvnce546j6xkngqfw0h84qj",
    "revised_at": null,
    "updated_at": "2026-03-16T15:49:10.214506971Z",
    "business_id": null,
    "custom_data": { "subscription_type": "default" },
    "customer_id": "ctm_01kkkxwav252thrbh9wnt577jr",
    "discount_id": null,
    "receipt_data": null,
    "currency_code": "USD",
    "billing_period": {
      "ends_at": "2026-03-17T15:48:15.785146Z",
      "starts_at": "2026-03-16T15:48:15.785146Z"
    },
    "invoice_number": "81667-10017",
    "billing_details": null,
    "collection_mode": "automatic",
    "subscription_id": "sub_01kkky4p16q20d5pvdz50ch6eh"
  }
}

  1. transaction.completed (status: completed)
{
  "event_id": "evt_01kkvncfeep1yg680a261n1090",
  "event_type": "transaction.completed",
  "occurred_at": "2026-03-16T15:49:10.222274Z",
  "notification_id": "ntf_01kkvncg18kaybwrgs3zvtqr3c",
  "data": {
    "id": "txn_01kkvncb7svc6x9302fnmgn9ca",
    "items": [
      {
        "price": {
          "id": "pri_01kkh7y92t7qpfnejczb2qstz6",
          "name": "Subscription",
          "type": "standard",
          "status": "active",
          "quantity": { "maximum": 999999, "minimum": 1 },
          "tax_mode": "location",
          "created_at": "2026-03-12T14:41:49.14661Z",
          "product_id": "pro_01kkh7s8fhc8s21zdf7bhbvmd2",
          "unit_price": { "amount": "14000", "currency_code": "USD" },
          "updated_at": "2026-03-13T14:25:30.083513Z",
          "custom_data": null,
          "description": "Subscription - Monthly",
          "trial_period": null,
          "billing_cycle": { "interval": "day", "frequency": 1 },
          "unit_price_overrides": []
        },
        "quantity": 1,
        "proration": null
      }
    ],
    "origin": "subscription_recurring",
    "status": "completed",
    "details": {
      "totals": {
        "fee": "750",
        "tax": "2333",
        "total": "14000",
        "credit": "0",
        "balance": "0",
        "discount": "0",
        "earnings": "10917",
        "subtotal": "11667",
        "grand_total": "14000",
        "currency_code": "USD",
        "grand_total_tax": "2333",
        "credit_to_balance": "0"
      },
      "line_items": [
        {
          "id": "txnitm_01kkvncb8pggz1wbrt90wmr9qf",
          "totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667" },
          "product": {
            "id": "pro_01kkh7s8fhc8s21zdf7bhbvmd2",
            "name": "Plan",
            "type": "standard",
            "status": "active",
            "image_url": null,
            "created_at": "2026-03-12T14:39:04.689Z",
            "updated_at": "2026-03-12T14:39:04.689Z",
            "custom_data": {
              "slug": "plan_2",
              "type": "plan",
              "credit_amount": "400000",
              "max_topup_credits_per_month": "1100000"
            },
            "description": null,
            "tax_category": "saas"
          },
          "price_id": "pri_01kkh7y92t7qpfnejczb2qstz6",
          "quantity": 1,
          "tax_rate": "0.2",
          "unit_totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667"
}
        }
      ],
      "payout_totals": {
        "fee": "750",
        "tax": "2333",
        "total": "14000",
        "credit": "0",
        "balance": "0",
        "discount": "0",
        "earnings": "10917",
        "fee_rate": "0.05",
        "subtotal": "11667",
        "grand_total": "14000",
        "currency_code": "USD",
        "exchange_rate": "1",
        "grand_total_tax": "2333",
        "credit_to_balance": "0"
      },
      "tax_rates_used": [
        {
          "totals": { "tax": "2333", "total": "14000", "discount": "0", "subtotal": "11667" },
          "tax_rate": "0.2"
        }
      ],
      "adjusted_totals": {
        "fee": "750",
        "tax": "2333",
        "total": "14000",
        "earnings": "10917",
        "subtotal": "11667",
        "grand_total": "14000",
        "retained_fee": "0",
        "currency_code": "USD",
        "grand_total_tax": "2333"
      }
    },
    "checkout": {
      "url": "https://example.com/checkout?_ptxn=txn_01kkvncb7svc6x9302fnmgn9ca"
    },
    "payments": [
      {
        "amount": "14000",
        "status": "captured",
        "created_at": "2026-03-16T15:49:06.042973Z",
        "error_code": null,
        "captured_at": "2026-03-16T15:49:08.212287Z",
        "method_details": {
          "card": { "type": "visa", "last4": "5556", "expiry_year": 2027, "expiry_month": 12,
"cardholder_name": "Customer" },
          "type": "card",
          "south_korea_local_card": null
        },
        "payment_method_id": "paymtd_01kkky4jk2g3p7cqr058y5n6j2",
        "payment_attempt_id": "16efcbef-0d41-428e-a2c6-920d4d9f7836",
        "stored_payment_method_id": "13663bc1-ae48-4316-99bb-ea165236ef70"
      }
    ],
    "billed_at": "2026-03-16T15:49:05.913853Z",
    "address_id": "add_01kkkxzss20bmqjny4f96gqad7",
    "created_at": "2026-03-16T15:49:05.964293Z",
    "invoice_id": "inv_01kkvnce546j6xkngqfw0h84qj",
    "revised_at": null,
    "updated_at": "2026-03-16T15:49:10.214506971Z",
    "business_id": null,
    "custom_data": { "subscription_type": "default" },
    "customer_id": "ctm_01kkkxwav252thrbh9wnt577jr",
    "discount_id": null,
    "receipt_data": null,
    "currency_code": "USD",
    "billing_period": {
      "ends_at": "2026-03-17T15:48:15.785146Z",
      "starts_at": "2026-03-16T15:48:15.785146Z"
    },
    "invoice_number": "81667-10017",
    "billing_details": null,
    "collection_mode": "automatic",
    "subscription_id": "sub_01kkky4p16q20d5pvdz50ch6eh"
  }
}

Steps To Reproduce

  1. Setup Paddle Cashier
  2. Process transactions via the in-built webhook
  3. Observe that occasionally transactions will list a status of paid despite the Paddle dashboard listing them as completed with a 2xx response code notification being sent in the logs

貢獻者指南