laravel/cashier-paddle

Possible webhook race condition

Open

#310 aberto em 17 de mar. de 2026

Ver no GitHub
 (1 comment) (0 reactions) (0 assignees)PHP (71 forks)github user discovery
help wanted

Métricas do repositório

Stars
 (268 stars)
Métricas de merge de PR
 (Métricas PR pendentes)

Description

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

Guia do colaborador