Skip to content

KuCoin Pay cancel webhook is never registered: two @Post decorators on one handler #4552

Description

@TaprootFreak

What happens

POST /v1/paymentLink/integrations/kucoin/webhook/cancel exists in the source but is never
registered as a route
, so a caller receives a 404.

src/subdomains/core/payment-link/controllers/c2b-payment-link.controller.ts:69-70:

@Post('integrations/kucoin/webhook/success')
@Post('integrations/kucoin/webhook/cancel')
@ApiExcludeEndpoint()
@HttpCode(200)
@UseGuards(KucoinPayWebhookGuard)
async kucoinPayWebhook(@Body() dto: any): Promise<{ returnCode: string; returnMessage: string }> {

Two routing decorators sit on the same handler. Nest stores a single path per handler
(PATH_METADATA is one value, not a list), so the decorator applied last wins and only
.../webhook/success ends up registered. This is confirmed by the routes the framework maps at
startup: .../webhook/success appears, .../webhook/cancel does not.

How severe this is — honestly, unclear

The route is definitely missing. Whether that costs anything depends on facts not visible in
this repo:

  • The webhook URL is never sent to the provider from our code — there is no webhook reference
    in src/integration/kucoin-pay/, only credentials in config. The callback URLs appear to be
    configured on the provider side, so whether cancel notifications are addressed to
    .../cancel at all cannot be answered here.
  • Even if they were delivered, the handler ignores the path and dispatches purely on the body,
    and its switch only acts on C2BPaymentStatus.COMPLETED. A cancel event would be
    acknowledged with 200 and otherwise do nothing.

So the realistic impact is that the provider may be receiving 404s for cancel callbacks —
possibly triggering retries on their side — rather than payments being mishandled. Someone with
access to the provider configuration should confirm which URLs are registered.

Fix options

  1. If the provider only ever calls .../success: drop the second decorator, the dead path
    disappears.
  2. If both URLs are configured: give cancel its own handler method, or register the paths as an
    array on one decorator.

Either way the endpoint inventory in docs/endpoints.md should be updated with it.

How this surfaced

Building the endpoint inventory (#4551) included a cross-check of the decorator-derived list
against the routes registered at startup. Every one of the 526 distinct method/path pairs
matched except this one, which exists only in the source.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions