Skip to content

Ensure Gateway ID validation consistency in ProcessWebhook #13

Description

@mushrafmim

Issue: Missing Gateway Validation in Webhook Processing

Description

In the payment package, incoming webhook requests are processed via the ProcessWebhook method in payment/service.go. Currently, this method retrieves a transaction using only the parsed reference number from the webhook payload. However, it does not verify that the transaction's stored GatewayID matches the gatewayID parameter provided in the URL route (/api/v1/payments/:gatewayID/webhook).

This omission allows one payment gateway (or any client spoofing that gateway's route) to potentially alter or settle transaction records belonging to another gateway, creating a data integrity risk and security vulnerability.

Similar validation is correctly performed in the ValidateReference flow (used for real-time reference checking).


Impact

  • Cross-Gateway Mutation: A webhook routed to /api/v1/payments/gateway-a/webhook could succeed in marking a transaction as successful even if that transaction was initialized under gateway-b.
  • Security & Auditing: Weakens the separation of duties and trust boundaries between different payment providers.

Code References

  • Target File: service.go
  • Location: Inside the repository transaction block in ProcessWebhook: service.go#L278-L323

Proposed Solution

Add a validation check inside the RunInTransaction block in ProcessWebhook to ensure the gateway identifiers match before modifying the transaction state.

// Inside ProcessWebhook in service.go
err = s.repo.RunInTransaction(ctx, func(repo PaymentRepository) error {
    tx, err := repo.GetByReferenceNumberForUpdate(ctx, gwPayload.ReferenceNumber)
    if err != nil {
        return fmt.Errorf("failed to retrieve transaction by reference: %w", err)
    }
    if tx == nil {
        return fmt.Errorf("reference %s: %w", gwPayload.ReferenceNumber, ErrTransactionNotFound)
    }

    // NEW VALIDATION: Ensure the webhook matches the gateway configured for this transaction
    if tx.GatewayID != gatewayID {
        return fmt.Errorf("gateway mismatch for transaction %s: expected %s, got %s: %w", 
            tx.ReferenceNumber, tx.GatewayID, gatewayID, ErrTransactionNotFound)
    }

    // ... existing status and amount checks ...
})

By returning ErrTransactionNotFound (or a similar error), the HTTP handler will return a 404 Not Found (or 422 Unprocessable Entity), signaling to the gateway to stop retrying the mismatched payload.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions