Skip to content

[BWS] Feat: webhooks support for external services - #4216

Open
Gamboster wants to merge 7 commits into
bitpay:masterfrom
Gamboster:feat/externalServicesWebhooks
Open

[BWS] Feat: webhooks support for external services#4216
Gamboster wants to merge 7 commits into
bitpay:masterfrom
Gamboster:feat/externalServicesWebhooks

Conversation

@Gamboster

@Gamboster Gamboster commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Description

Ticket Reference: RN-2714

This PR adds webhook endpoints for all six partners so BWS can react to status changes (processing, completed, failed, refunded, etc.) in real time instead of relying purely on client-side polling, while guarding against the usual webhook pitfalls: unverified senders, duplicate/retried deliveries, and out-of-order delivery.

Webhook processing will be rolled out gradually: Not all endpoints will be operational immediately. Initially, we will enable the Moonpay webhook endpoint until we verify that everything is working as expected with this single partner. (Until we enable webhooks in the configuration of each external service, the endpoints will not receive traffic.) Then we will continue with the other partners, one by one, until all are operational simultaneously.
The first thing we will do upon receiving a call is check that it is not a duplicate or out-of-order status and then send an event to Braze (a requirement of the marketing team). Any additional processing that needs to be done will be added in the future in another PR.

Changelog

  • Added POST /v1/service/{partner}/webhook routes in a new src/lib/routes/webhooks.ts, with a shared handler that verifies the signature, persists the event, and always responds so the partner doesn't retry unnecessarily.
  • Implemented per-partner signature verification against each partner's documented scheme:
    • MoonPay: HMAC-SHA256 (Moonpay-Signature-V2: t=...,s=...), with support for both the standard and embedded webhook secrets.
    • Simplex: RS256 JWT in X-Signature-SHA256, including expiry (exp) enforcement.
    • Ramp: ECDSA (secp256k1/SHA-256) signature over a deterministic (fast-json-stable-stringify-equivalent) serialization of the body, sent in X-Body-Signature.
    • Banxa: HMAC-SHA256 over POST\n{path}\n{nonce}\n{body}, sent via Authorization: Bearer {apiKey}:{signature}:{nonce}.
    • Sardine: HMAC-SHA256 via X-Sardine-Signature.
    • Transak: HS256 JWT verified against the partner's Access Token (POST /partners/api/v2/refresh-token), now fetched and cached per environment instead of a static secret.
  • Added idempotency/deduplication for webhook deliveries: a new onramp_webhook_events collection (storage.ts) keyed by a deterministic id (partner + env + event + externalId + delivery version), so retried deliveries from a partner are safely ignored.
  • Added out-of-order delivery detection, reusing the same collection/index to detect when a partner delivers an older status after a newer one and skip re-processing it.
  • Added a minimal Braze REST client (src/lib/braze.ts) that fires a "BWS - ONRAMP Webhook Received" track event per unique delivery (skipped for duplicates/stale events and when no user id is available).
  • Added a mechanism for Ramp to correlate webhooks back to a BitPay user: since Ramp never includes user details in its webhook payloads, BWS now appends a userId query param to the signed webhookStatusUrl/offrampWebhookV3Url it generates, which Ramp echoes back on the webhook call.
  • Added OnrampWebhookEvent model (src/lib/model/onrampWebhookEvent.ts) as the common shape used across all partners' webhook handlers and storage.
  • Added new config fields (bws.example.config.js / config.ts) for webhook secrets/signing keys per partner and environment.
  • Added unit tests (test/integration/externalservices/*.test.ts) covering signature verification (valid/invalid/missing signature, missing secret configured), payload parsing, and error handling for each partner's new *HandleWebhook method.

Testing Notes

  • Each partner's webhook secret/signing key needs to be configured (see bws.example.config.js) for signature verification to be enforced; if left unset, verification is skipped with a warning logged (useful for local testing without real credentials, but should be set in staging/production).
  • Unit tests (npm test in packages/bitcore-wallet-service) exercise the signature verification and field-mapping logic per partner in isolation, including duplicate/invalid-signature/missing-header/missing-config edge cases.

@Gamboster
Gamboster marked this pull request as draft August 10, 2026 19:29
@Gamboster
Gamboster force-pushed the feat/externalServicesWebhooks branch from 0ee627c to 15d9dc4 Compare August 13, 2026 15:56
@Gamboster
Gamboster force-pushed the feat/externalServicesWebhooks branch from aae7a67 to 0b13750 Compare August 18, 2026 19:04
@Gamboster
Gamboster marked this pull request as ready for review August 19, 2026 14:06
@Gamboster Gamboster changed the title WIP: [BWS] Feat: webhooks support for external services [BWS] Feat: webhooks support for external services Aug 19, 2026
@Gamboster
Gamboster force-pushed the feat/externalServicesWebhooks branch from bd4a5be to 5491731 Compare August 19, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant