Skip to content

Developers Reference

Reference

Every endpoint, status, error code and webhook event. The endpoints are generated from the OpenAPI 3.1 contract, so this page cannot describe a parameter the API does not have.

Endpoints

Grouped as the contract groups them. A * marks a required field. Paths are relative to https://www.payshen.com.

Events

  • get/api/v1/events

    List events across all payments

    The polling equivalent of your webhook endpoint. Scoped to the key's environment.

    FieldTypeNotes
    typestringqueryExact event type, e.g. payment.captured.
    beforestringqueryCursor: the last id from the previous page.
    limitinteger ≥ 1query
    200
    A page of events.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    422
    validation_error.
    429
    Rate limited. Retry after the interval in the Retry-After header.
  • get/api/v1/payments/{id}/events

    List a payment's timeline

    FieldTypeNotes
    id*stringpathThe payment id returned as payment_id on creation.
    200
    Events, oldest first.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    404
    not_found.
    429
    Rate limited. Retry after the interval in the Retry-After header.

Partners

  • get/api/v1/merchants

    List your sub-merchants

    200
    The partner's merchants.
    401
    Missing, malformed, revoked or unknown API key.
    403
    not_a_partner.
    429
    Rate limited. Retry after the interval in the Retry-After header.
  • post/api/v1/merchants

    Provision a sub-merchant

    White-label partners only. Requires a live key and the partner flag on your workspace, both enforced server-side.

    201
    The sub-merchant workspace was created and its owner invited.
    401
    Missing, malformed, revoked or unknown API key.
    403
    not_a_partner, or live_key_required when called with a test key.
    409
    already_invited.
    422
    validation_error.
    429
    merchant_limit - the partner's allowance is exhausted.

Payments

  • get/api/v1/openapi.jsonno auth

    This document

    The machine-readable contract, unauthenticated so it can be read and imported before you have an account.

    200
    An OpenAPI 3.1 document.
  • get/api/v1/payments

    List payments

    Newest first, cursor paginated on payment id. Scoped to the key's environment.

    FieldTypeNotes
    statusstringqueryCase-insensitive.
    beforestringqueryCursor: the last id from the previous page.
    limitinteger ≥ 1query
    200
    A page of payments.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    422
    validation_error - an unknown status filter.
    429
    Rate limited. Retry after the interval in the Retry-After header.
  • post/api/v1/payments

    Create a payment

    Routes the payment across your active connections. A second connection is tried only after a soft decline or a technical error before anything was executed (per routing mode), never after a timeout or an unknown result, which answers UNKNOWN. With return_url the response is a redirect instead of an outcome.

    FieldTypeNotes
    Idempotency-Keystringheader8-200 printable ASCII characters. Scoped to your workspace and environment. Required when the API key's environment is production (a ps_live_ key): such a request without it is refused 400 idempotency_key_required. Optional in sandbox (ps_test_). Once a provider may have received the request, every retry under this key is answered from what Payshen recorded and never sent to a provider again: the stored response, else the payment's current state, else UNKNOWN. The key is kept 24 hours after its answer, and for as long as the provider attempt has no answer or its payment is UNKNOWN; after that the same key is a new request.
    amount*integer ≥ 50Minor units of currency. 5000 is EUR 50.00, and JPY 5,000.
    currency*stringISO 4217.
    country*stringISO 3166-1 alpha-2 of the customer.
    method"card" | "bank" | "wallet" | "crypto"
    card_brandstring
    referencestringYour own identifier. Echoed back and in webhooks.
    initiator"cit" | "mit"mit is merchant-initiated, e.g. recurring.
    capture_method"automatic" | "manual"
    return_urlstringPresent to use the hosted redirect flow.
    cancel_urlstring
    debugbooleanWhen true, routing.excluded lists every connection removed before routing, with a code and a reason, and a routing_failed error carries the same list. Off by default: the list names your connections and their approval terms. Ignored on the hosted redirect branch, which does not route until the customer acts.
    201
    Created. Either an outcome, or a redirect to complete it.
    400
    idempotency_key_required (a live key sent no Idempotency-Key), invalid_idempotency_key or invalid_json.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    409
    idempotency_in_progress, or routing_failed when no connection can take it. With debug: true a routing_failed error also carries error.excluded, the same list as routing.excluded.
    413
    payload_too_large - the body exceeds 16 KB.
    422
    validation_error, or idempotency_key_reuse for a reused key with a different body.
    429
    Rate limited. Retry after the interval in the Retry-After header.
    500
    internal_error - nothing was sent to a provider; safe to retry with the same Idempotency-Key. payment_result_unknown - a provider may have received the payment and its result could not be recorded yet; the error carries request_id. Do not retry with a new Idempotency-Key. A retry with the same key never charges again: it answers 409 while the first request's claim is fresh (15 minutes), then the payment as Payshen recorded it (often UNKNOWN). A daily job records such a payment even if nobody retries.
  • get/api/v1/payments/{id}

    Retrieve a payment

    FieldTypeNotes
    id*stringpathThe payment id returned as payment_id on creation.
    200
    The payment.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    404
    not_found. Also returned for a payment belonging to another workspace or environment.
    429
    Rate limited. Retry after the interval in the Retry-After header.
  • post/api/v1/payments/{id}/capture

    Capture an authorization

    Full or partial. Only a payment created with capture_method: manual can be captured.

    FieldTypeNotes
    id*stringpathThe payment id returned as payment_id on creation.
    amountinteger ≥ 1Minor units. Omit to capture the full authorization.
    200
    Captured.
    400
    invalid_idempotency_key - the Idempotency-Key is not 8-200 printable ASCII characters.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    404
    not_found.
    409
    not_capturable - the payment is not AUTHORIZED (already captured, voided, or a capture or void is in flight or unknown); capture_conflict - another capture or void took it first; no_provider_ref - there is no provider reference to capture; idempotency_in_progress - a request with this Idempotency-Key is still being processed.
    422
    validation_error or invalid_amount - the amount is not a positive integer or exceeds the authorization; idempotency_key_reuse - the key was used with a different amount.
    429
    Rate limited. Retry after the interval in the Retry-After header.
    500
    state_unconfirmed - the provider was asked and its answer could not be recorded (the message says whether it confirmed, refused or did not answer); state_conflict - the provider confirmed the capture and the payment had meanwhile moved. Do not retry: the same Idempotency-Key replays this answer, and the payment is checked with the provider by Payshen operations.
    501
    unsupported - the payment's provider cannot capture.
    502
    psp_error - the provider refused the capture (the message is the provider's): nothing was captured and the payment is AUTHORIZED again; the Idempotency-Key is released, so a new attempt is safe. capture_result_unknown - the provider did not answer and may have captured: the payment is UNKNOWN. Do NOT retry; the same Idempotency-Key replays this answer, and Payshen settles the payment from the provider's record.
  • post/api/v1/payments/{id}/refunds

    Refund a captured payment

    Full or partial. The amount is clamped to the refundable balance, and concurrent full refunds cannot both succeed. Send an Idempotency-Key: on this endpoint it names the refund itself, so a request repeated under the same key is answered from that refund and never refunds twice. For 24 hours the stored response is replayed as it was sent; after that, the same key on the same payment is answered from the refund's current record (its final result once it is settled). A refund the provider has not confirmed yet is counted in refund_pending_amount, not in refunded_amount, and is settled from the provider's own record: a refund still pending is never answered as refunded.

    FieldTypeNotes
    id*stringpathThe payment id returned as payment_id on creation.
    Idempotency-Keystringheader8-200 printable ASCII characters. The same key on the same payment is the same refund: a repeat answers that refund's result (or 409 refund_pending while the provider has not confirmed it) and never reaches the provider again. Within 24 hours a repeat replays the stored response; after 24 hours it is answered from the refund's record. The same key with a different amount is refused.
    amountinteger ≥ 1Minor units. Omit to refund the full refundable balance.
    reasonstring
    200
    Refunded: the provider confirmed it.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    404
    not_found.
    409
    refund_pending - this refund, or one holding the rest of the balance, is not confirmed yet: the provider accepted it and is still processing it, or its answer is still being settled. It is not refunded, and its amount is in refund_pending_amount. Do not retry with a new key; the same Idempotency-Key answers its final result once it is settled. Also not_refundable and refund_conflict.
    422
    validation_error or invalid_amount - nothing refundable, or an invalid amount; idempotency_key_reuse - the key was used with a different amount.
    429
    Rate limited. Retry after the interval in the Retry-After header.
    500
    state_unconfirmed - the provider confirmed the refund, and either recording it failed or the payment's status could not be updated after it (the message says which). Do not retry: the same Idempotency-Key replays this answer for 24 hours, and after that is answered from the refund's record.
    502
    psp_error - the provider refused the refund: nothing was refunded and the amount is refundable again. The same Idempotency-Key answers the same refusal; a retry with a NEW Idempotency-Key is a new refund and is safe. refund_result_unknown - the provider did not answer and may have refunded: the amount stays in refund_pending_amount. Do NOT retry, with any key; Payshen settles it from the provider's record and the payment shows the result.
  • post/api/v1/payments/{id}/void

    Void an authorization

    Releases an uncaptured authorization. A captured payment must be refunded instead.

    FieldTypeNotes
    id*stringpathThe payment id returned as payment_id on creation.
    200
    Voided.
    400
    invalid_idempotency_key - the Idempotency-Key is not 8-200 printable ASCII characters.
    401
    Missing, malformed, revoked or unknown API key.
    403
    The workspace is suspended, or the key lacks the required scope.
    404
    not_found.
    409
    not_voidable - the payment is not AUTHORIZED (already captured, voided, or a capture or void is in flight or unknown); void_conflict - another capture or void took it first; no_provider_ref - there is no provider reference to void; idempotency_in_progress - a request with this Idempotency-Key is still being processed.
    429
    Rate limited. Retry after the interval in the Retry-After header.
    500
    state_unconfirmed - the provider was asked and its answer could not be recorded (the message says whether it confirmed, refused or did not answer); state_conflict - the provider confirmed the void and the payment had meanwhile moved. Do not retry: the same Idempotency-Key replays this answer, and the payment is checked with the provider by Payshen operations.
    501
    unsupported - the payment's provider cannot void.
    502
    psp_error - the provider refused the void (the message is the provider's): the payment is AUTHORIZED again; the Idempotency-Key is released, so a new attempt is safe. void_result_unknown - the provider did not answer and may have voided: the payment is UNKNOWN. Do NOT retry; the same Idempotency-Key replays this answer, and Payshen settles the payment from the provider's record.

Providers

  • post/api/v1/psp-webhooks/{pspId}no auth

    Inbound provider webhook

    Called by a payment provider, not by you. Requires an HMAC signature issued to that provider. Documented so integrators understand the inbound path, not so they call it.

    FieldTypeNotes
    pspId*stringpath
    X-Payshen-Signature*stringheadert=<unix>,v1=HMAC_SHA256(secret, "<t>.<body>").
    200
    Accepted, stored and applied.
    401
    Missing or invalid signature. An unknown provider id answers identically, by design.
    429
    Rate limited.

Statuses

The unified status set, UPPERCASE on the wire. A payment only moves forward: once captured, settled, refunded or disputed it does not go back to an earlier state.

StatusMeaning
CREATEDThe payment exists and nothing has been attempted at a provider yet.
PENDINGThe provider has it and is processing. In flight, not an outcome.
REQUIRES_ACTIONWaiting on the customer. Send them to next_action.url.
AUTHORIZEDApproved and held, not yet captured. Capture it or void it.
CAPTUREDApproved and captured at the provider.
DECLINEDThe provider refused it. decline_category carries the normalized reason.
FAILEDIt could not be completed, and will not be. The payment's timeline records why.
UNKNOWNThe provider may have executed the operation and we could not tell (a timeout after the request was sent, a connection lost mid-answer).
CANCELLEDEnded before any money was taken: an authorization voided, or the customer cancelled on the hosted page.
REFUNDEDThe whole captured amount has been refunded.
PARTIALLY_REFUNDEDPart of the captured amount has been refunded; the rest can still be.
CHARGEBACKA chargeback was recorded against it.
SETTLEDThe captured funds appeared in a settlement report matched to this payment.

UNKNOWN and PENDING

Treat both as in flight. Never as a decline, and never retry the capture or void yourself: the first one may already have happened. Read the payment again later; it moves to a confirmed status once the provider's own record settles the question.

Error codes

Every failure is { "error": { "code", "message" } }. Switch on code, which is stable under the v1 freeze. The message is for people and may change.

CodeHTTPMeaning
invalid_json400The body is not valid JSON.
idempotency_key_required400POST /v1/payments with a production (ps_live_) key sent no Idempotency-Key. Sandbox (ps_test_) keys may omit it.
invalid_idempotency_key400The Idempotency-Key header is not 8 to 200 printable ASCII characters.
unauthorized401The API key is missing, malformed, revoked or unknown.
tenant_suspended403The workspace is suspended.
not_a_partner403A partner endpoint was called by a workspace that is not a partner.
live_key_required403Provisioning a merchant was attempted with a test key.
not_found404No such payment. Also returned for a payment in another workspace or environment.
idempotency_in_progress409A request with the same Idempotency-Key is still being processed. Retry shortly.
routing_failed409No connection can take this payment.
already_invited409That owner already has a pending invitation. Safe to treat as done.
payload_too_large413The body exceeds 16 KB.
validation_error422A field is missing or invalid, or the payment is not in a state that allows the operation.
idempotency_key_reuse422The Idempotency-Key was used before with a different body.
rate_limited429Too many requests. Wait for the Retry-After interval.
merchant_limit429The partner's merchant allowance is used up.
internal_error500Something failed on our side. Safe to retry with the same Idempotency-Key.
payment_result_unknown500A provider may have received the payment and its result could not be recorded yet. Retry only with the same Idempotency-Key, never a new one: it answers 409 for up to 15 minutes, then the payment as recorded.

A 4xx is an answer: retrying it unchanged gets the same answer. A 5xx, a timeout or a 429 is worth retrying with the same Idempotency-Key.

Webhook events

Each delivery is a JSON envelope with id, type, created and data, signed in the X-Payshen-Signature header. An endpoint can subscribe to all of these or a subset. Sandbox endpoints receive only sandbox events, production endpoints only production events.

EventSent when
payment.createdA hosted payment was created and is waiting for the customer.
payment.approvedThe payment was approved and captured in one step.
payment.authorizedThe payment was approved with capture_method manual, and is held for capture.
payment.capturedAn authorization was captured, in full or in part. Also sent when Payshen confirms with the provider that a payment it reported as declined, failed or cancelled was in fact collected: then corrected is true and previous_status is the status you were told. Do not charge the customer again.
payment.declinedThe payment was declined.
payment.unknownThe provider did not give a usable answer to the charge, so it may have gone through. Treat it as in flight, never as declined, and do not charge again.
payment.failedNo connection could take the charge: each refused the request before executing it (a rejected API key, an invalid request, a rate limit). Nothing was charged, and it is not a decline.
payment.voidedAn authorization was voided.
payment.cancelledThe customer cancelled on the hosted page.
payment.refundedA refund was made, in full or in part.
payment.refund_failedA refund that was waiting on the provider (refund_pending_amount) did not go through. Its amount is no longer reserved and was not refunded.
webhook.testSomeone pressed Send test event in the dashboard. It never describes a real payment.

Deduplicate on event.id

A delivery that times out or answers 5xx is sent again, so the same event can arrive twice. Record each id and skip what you have already processed. claimWebhookEvent does this in one call.

API reference - Payshen