API referencepayments
Receive psp webhook
POST
/v1/payments/psp-webhook/{provider}The payment provider's asynchronous notification — the async source of truth for money state
Provider keys served: harness, razorpay (x-razorpay-signature over the raw body; dedupe on x-razorpay-event-id; payment.authorized, payment.captured and order.paid trigger a provider read, every other…Show more
Provider keys served: harness, razorpay (x-razorpay-signature over the raw body; dedupe on x-razorpay-event-id; payment.authorized, payment.captured and order.paid trigger a provider read, every other event is recorded only) and payu (a signed form callback; a non-PAID status is recorded only). Called by the payment provider, not by a tenant, so it carries no platform credential (security is empty). Instead its signature is verified server-side against the provider's configured webhook secret before anything is read: a missing, invalid or stale signature, or a provider with no verification config, is 401 UNAUTHENTICATED and nothing is applied (MH-15 bad-signature half; the edge accepts nothing it cannot verify). A verified notification drives the PaymentIntent state machine and is surfaced to tenants as payment.captured / payment.failed / payment.refunded / payment.canceled. Redelivery is normal: a notification already seen is 200 DUPLICATE with zero new state transitions (MH-15 dedupe), never a 409, so a provider does not retry forever. The body is the provider's own payload and is deliberately untyped here. Harness provider only today.Path parameters
- providerstringRequired
The payment-provider key behind the seam, e.g.Show more
The payment-provider key behind the seam, e.g. harness
Request body
Responses
- 200Verified; ACCEPTED (applied once) or DUPLICATE (already seen, nothing applied)PspWebhookAck
- 400VALIDATION. details.reason UNKNOWN_PAYMENT_REF (details.field paymentRef), CURRENCY_MISMATCH (details.field currency), AMOUNT_REQUIRED, or NOTICE_CONTRADICTS_PAYMENT.ApiErrorResponse
- 401ApiErrorResponse
UNAUTHENTICATED — the notification could not be verified (details.reason REJECTED_SIGNATURE | REJECTED_STALE | REJECTED_UNVERIFIABLE).Show more
UNAUTHENTICATED — the notification could not be verified (details.reason REJECTED_SIGNATURE | REJECTED_STALE | REJECTED_UNVERIFIABLE). Rejected and alarmed. - 405ApiErrorResponse
VALIDATION — the public edge serves GET, POST, PUT, PATCH and DELETE only; any other method is refused before the platform sees it (details.reason METHOD_NOT_ALLOWED, details.source edge).Show more
VALIDATION — the public edge serves GET, POST, PUT, PATCH and DELETE only; any other method is refused before the platform sees it (details.reason METHOD_NOT_ALLOWED, details.source edge). Not retryable as sent. - 413ApiErrorResponse
VALIDATION — the request body is larger than the surface's limit.Show more
VALIDATION — the request body is larger than the surface's limit. Not retryable as sent: shrink the body. Declared on every operation that takes a request body. details carries exactly one key, maxBodyBytes (integer): the limit that applies to this surface, in bytes. - 414ApiErrorResponse
VALIDATION — the request URI (path and query string) is longer than 8 KiB; the public edge refuses it before the platform sees it (details.reason URI_TOO_LONG, details.source edge).Show more
VALIDATION — the request URI (path and query string) is longer than 8 KiB; the public edge refuses it before the platform sees it (details.reason URI_TOO_LONG, details.source edge). Not retryable as sent: send long input in the body of a POST. - 415ApiErrorResponse
VALIDATION — a request body that is not JSON (application/json or application/*+json; a form body only on the OAuth token routes).Show more
VALIDATION — a request body that is not JSON (application/json or application/*+json; a form body only on the OAuth token routes). The public edge refuses it before the platform sees it (details.reason UNSUPPORTED_MEDIA_TYPE, details.source edge). Not retryable as sent: send Content-Type application/json. Declared on every operation that takes a JSON request body. - 500INTERNAL — a bug, and traceId is the support handle.ApiErrorResponse
- 501ApiErrorResponse
NOT_IMPLEMENTED — this deployment serves no payments: no payment provider is configured (provider=none), so every payment route answers 501.Show more
NOT_IMPLEMENTED — this deployment serves no payments: no payment provider is configured (provider=none), so every payment route answers 501. Not a capabilities-doc bug: payments are deployment configuration.
Errors you should handle
Branch on code and honour retryable — never on the HTTP status alone.
| 400 | VALIDATION The notice's amount contradicts the payment it names; it was not applied. | not retryable |
| 401 | UNAUTHENTICATED payment-provider webhook rejected at the ingestion edge | not retryable |
| 405 | VALIDATION This method is not served. | not retryable |
| 413 | VALIDATION The request body is larger than this API accepts (262144 bytes). | not retryable |
| 414 | VALIDATION The request URI is longer than 8 KiB. | not retryable |
| 415 | VALIDATION Send application/json. | not retryable |
| 500 | INTERNAL An unexpected error occurred. Quote the traceId to support. | retryable |
| 501 | NOT_IMPLEMENTED This deployment serves no payments: no payment provider is configured. | not retryable |