Wise Webhook Receiver

Inbound Wise webhook receiver -- AP2 auto-settlement (MP #42).

Contract (always-200):

  • HTTP 200 on every path, including signature failure, parse failure, unknown event type, and DB error. Failures are recorded via StructuredLogger; the JSON body status + reason fields describe the outcome.

Validation order (MP rule #11):

  1. Read raw body + headers
  2. RSA-SHA256 signature verification against Wise's PUBLISHED production public key (NO DB operations until this passes)
  3. JSON parse
  4. X-Test-Notification handling (Wise sends this to verify the callback URL when a subscription is being set up; we MUST respond 2xx)
  5. Event normalization via parse_wise_event
  6. Filter: must be an inbound-credit event with transaction_type == "credit" AND currency == "USD"
  7. 3-criteria settlement matcher

Security (MP rule #8, Path A):

  • No shared secret exists in the Wise protocol; verification uses the public key embedded at module-scope (safe -- public material)
  • X-Signature-SHA256 header value is logged only as an 8-char prefix for forensic correlation
  • The cryptography library handles constant-time RSA verification internally; no application-level timing leaks

Coexistence with execute-time poll (check_wise_inbound_transfer): The existing poll runs synchronously during /api/ap2/execute and may have already set settlement_confirmed=true on a row. This webhook UPDATEs the same row; the matcher only considers rows with settlement_confirmed=false in its candidate pool, so a previously-confirmed row is silently passed over (no double- confirm, no false alarm).

post/api/webhooks/wise

Request

  • Base URL: https://api.merchant-0.com (the document declares no server; this is the origin it was published from)
  • URL: https://api.merchant-0.com/api/webhooks/wise
  • Auth: none declared

Response

Successful Response

unknown required

Changes

No recorded changes to this operation across all 1 revision of this API.