How to integrate Canonical

Learn how the Canonical PSS driver lets you connect a payment service provider to B2CORE by implementing a standard deposit API contract, including B2CORE-side configuration, webhooks, polling, and the deposit status lifecycle.

The Canonical driver lets you connect a payment service provider (PSP) of your choice to B2CORE through PSS, even when B2CORE does not yet offer a dedicated driver for that provider.

Why use the Canonical driver

Most payment systems in B2CORE rely on a dedicated driver built specifically for one provider. The Canonical driver takes a different approach: it defines a single, standard API contract — the Canonical Deposit API — that any provider can implement. B2CORE then handles authentication, the deposit start flow, polling, webhooks, and the status lifecycle in a uniform way, regardless of which provider sits behind the contract.

The Canonical driver is useful when you want to:

  • Connect a preferred or in-house PSP that has no dedicated B2CORE driver, without waiting for custom development.
  • Reduce time to market by having your provider implement one documented, stable contract instead of a bespoke integration.
  • Keep full control of the provider side, while B2CORE manages the deposit workflow on its side.

The Canonical driver currently supports deposit flows. To offer deposits through your provider, the provider must implement the Canonical Deposit API described in the OpenAPI specification below and follow the behavioral requirements on this page.

OpenAPI specification

The Canonical Deposit API is defined in the following OpenAPI specification, which covers authentication, request and response schemas, endpoints, and status codes. Download it to review the full contract that your provider must implement:

The rest of this page describes the behavioral requirements, B2CORE-side configuration, and design decisions that the specification cannot express. Read both together for a complete picture of the integration.

B2CORE driver credentials

These fields are configured on the B2CORE side (Back Office) and used to authenticate with the PSP API. See the OpenAPI specification for full JWT token generation and verification details.

FieldDescription
API base URLHTTPS base URL of the PSP API.
App IDUnique merchant identifier. Used as the sub claim in the JWT.
App secretSecret key for HMAC-SHA256 signing. Base64 URL-encoded, 32 bytes (43 characters). Never sent in requests — used only to sign tokens.

B2CORE driver configuration fields

These fields are configured on the B2CORE side (Back Office) and control driver behavior. They are not part of the PSP-facing API.

Global parameters (globalParam1, globalParam2, globalParam3)

Three configuration-level string fields sent with every authenticated request to the PSP.

  • For POST endpoints, they are included in the JSON request body.
  • For GET endpoints, they are included as query parameters.

These represent PSP-specific values such as merchant ID, channel, or project ID. The exact semantics depend on the PSP implementation. The B2CORE admin fills them in during configuration setup.

Required fields (read-only)

Type: Multi-select

Selects which user info fields are displayed in the payment form as read-only (non-editable).

Selected fields must already be configured and saved in the user's B2CORE profile. If any selected field is missing from the profile, the payment form generation fails with a missing fields error — the user cannot proceed until the data is filled in their B2CORE profile.

The selected fields, along with the fields from Required fields (editable), determine which user data is meaningfully populated in the startDepositUserInfo object sent to the PSP in the POST /api/v1/deposits request. Fields not selected in either list are hidden in the form and may be sent as empty values.

Required fields (editable)

Type: Multi-select

Selects which user info fields are displayed in the payment form as editable. The user can fill in or modify these fields directly in the payment form. Unlike Required fields (read-only), there is no requirement for these fields to be pre-configured in the user's B2CORE profile.

Priority rule: If a field is selected in both Required fields (read-only) and Required fields (editable), it is displayed as read-only. The read-only setting always takes priority.

Email behavior: Email is always displayed in the payment form and always sent in the startDepositUserInfo, regardless of whether it is selected in either list. The configuration only controls how it is displayed:

Email selected inBehavior
Neither listDisplayed as an editable field
Required fields (editable)Displayed as an editable field
Required fields (read-only)Displayed as a read-only field (value from B2CORE profile)
Both listsDisplayed as a read-only field (read-only takes priority)

Default sync deadline

Type: Select
Default: 4h

Maximum duration after deposit creation during which B2CORE polls for the deposit status. After this deadline:

  • StatusSyncInProgress → the deposit is moved to unexpected.
  • StatusSyncUnexpected → the deposit is moved to unexpected.

The unexpected status requires manual admin investigation via the LifecycleService.

Safe to fail at start

Type: Boolean (currently hardcoded to yes, no choice)

Determines whether it is safe to mark a deposit as failed (terminal) when the start request encounters an unexpected error.

  • yes — if the PSP returns an error during deposit start, and B2CORE is confident the deposit was not created on the PSP side (for example, B2CORE never received a redirect URL), the deposit can safely be moved to failed. The client has not lost any money.
  • no — even on error, the deposit is moved to in_progress with an unverified external ID and polled, because the PSP might have created the deposit despite the error.

Currently, always yes. The typical case: without a redirect URL, the client cannot complete the PSP payment page, so the deposit cannot succeed.

Wait for webhook before polling

Type: Boolean (currently hardcoded to yes, no choice)

Controls whether B2CORE waits for a webhook notification before it starts polling GET /api/v1/deposits/{externalID}.

ValueBehavior
yesAfter deposit start, B2CORE waits up to 5 minutes for a webhook before it begins to poll. If no webhook arrives within 5 minutes, B2CORE proceeds to standard polling.
noB2CORE begins polling immediately according to the standard backoff schedule.

Rationale: Many PSPs send a webhook notification quickly when the deposit status changes. Waiting for the webhook before polling reduces the number of unnecessary API calls, which helps stay within rate limits. The 5-minute timeout ensures progress even if the webhook is delayed or lost.

Test configuration flow

When an admin clicks Test Configuration in B2CORE:

1. B2CORE generates a one-time JWT signed with appSecret
2. B2CORE → POST /api/v1/configuration/test (with Bearer JWT + globalParams)
3. If response status = "available" → test result: "available"
4. If response status = "failed" → test result: "failed" (with error from PSP)
5. If unexpected error (5xx, timeout, and similar) → test result: "unexpected"

The test configuration method is the only method where any credential issue is expected to return not 401 Unauthorized, but 200 OK with a response body.

The code and description are shown to the B2CORE administrator, so return clean, non-sensitive data.

Webhook system design

Two webhook channels

B2CORE supports two webhook channels per deposit:

ChannelRegistrationDescription
AutomaticVia notificationURL in the POST /api/v1/deposits requestAlways active. B2CORE generates the URL and passes it to the PSP.
Admin-configurableSet by the admin in the B2CORE Back OfficeOptional. The admin can configure a separate webhook URL that the PSP sends notifications to (for example, registered in the PSP's admin panel).

Both channels feed into the same B2CORE webhook handler → driver_transit store → poller optimization pipeline.

Webhook as polling optimization

The webhook is not the source of truth. It is an optimization that reduces unnecessary polling requests.

PSP → B2CORE webhook handler → driver_transit (key-value store) → poller

How it works:

  1. When a webhook arrives, B2CORE stores a flag in driver_transit keyed by externalID.
  2. The poller checks driver_transit before it makes an API call:
    • If a webhook flag exists for the externalID, B2CORE immediately calls GET /api/v1/deposits/{externalID}.
    • If no flag exists and less than 5 minutes have passed, B2CORE waits (see Wait for webhook before polling).
    • If no flag exists and more than 5 minutes have passed, B2CORE proceeds with standard polling.
  3. When the deposit reaches a terminal status (success or failed), the driver_transit entry is deleted.

Webhook payload

The webhook payload is minimal (see the webhook callback under POST /api/v1/deposits in the OpenAPI specification):

{
  "externalID": "550e8400-e29b-41d4-a716-446655440000",
  "status": "success"
}

The payload contains the following fields:

  • externalID — matches the UUID from the POST /api/v1/deposits request.
  • status — one of "success", "failed", or "unprocessable".

The webhook should be sent only when the deposit transitions to a terminal status.

Deposit start flow

Start request

B2CORE initiates a deposit by calling POST /api/v1/deposits.

The request includes a returnURL field — a B2CORE frontend page URL to redirect the user back to after PSP page interaction. This is not a webhook — it is a browser redirect only.

For all other details, see the OpenAPI specification.

Start result mapping

The PSP response maps to a B2CORE action as follows:

PSP responseB2CORE action
PSP returns a redirect URLMove to in_progress
2xx with a specification violation (for example, no redirect URL)Move to failed
HTTP 4xx / 5xx / timeout / network errorMove to failed

All failure scenarios result in failed (not unexpected) because Safe to fail at start is yes (see Safe to fail at start): without a valid redirect URL, the end user cannot interact with the PSP payment page, so no money can be lost.

Redirect flow

On a successful start, B2CORE receives a redirect URL and sends the end user to the PSP payment page:

sequenceDiagram
    participant B as B2CORE
    participant P as PSP
    participant U as End user

    B->>P: POST /api/v1/deposits
    P-->>B: 200 OK<br/>action.type: "redirect"<br/>action.redirect.url: "..."
    B->>U: Redirect end user to PSP payment page
    U->>P: Open PSP payment page
    U->>P: Complete payment
    P-->>U: Redirect to returnURL
    U->>B: Land on B2CORE "in progress" page
  • The returnURL takes the user back to a B2CORE frontend page that indicates the deposit is being processed.
  • After start, B2CORE begins the polling and webhook flow (see Polling and status sync).
  • Currently, "redirect" is the only supported action type.

Polling and status sync

Polling backoff schedule

B2CORE uses progressive backoff to poll GET /api/v1/deposits/{externalID}:

Time since deposit startPoll interval
0–15 minutesEvery 1 minute
15–60 minutesEvery 3 minutes
1–3 hoursEvery 5 minutes
3–5 hoursEvery 10 minutes
5+ hoursEvery 15 minutes

Deadline handling

Default deadline: 4 hours after deposit creation (configurable, see Default sync deadline).

When the deadline is exceeded, intermediate statuses are resolved as follows:

Last poll resultB2CORE action
inProgressMove to unexpected
Network error, 5xx, parse error, and similarMove to unexpected

The unexpected status stops automatic polling and requires manual admin action via B2CORE's LifecycleService.

Webhook wait logic

When Wait for webhook before polling is yes (current default, see Wait for webhook before polling):

flowchart TD
    A["t=0min: deposit created, redirect URL returned<br/>Poller scheduled but WAITS for webhook"] --> B{Webhook received<br/>before t=5min?}
    B -- yes --> C["Immediately poll<br/>GET /api/v1/deposits/{externalID}"]
    B -- "no (t=5min elapsed)" --> D["Begin standard polling<br/>(1-minute intervals initially)"]
    C --> E[Continue with standard<br/>polling backoff]
    D --> E
    E --> F[Continue until terminal status<br/>or deadline]

Polling result mapping

Each poll result from GET /api/v1/deposits/{externalID} maps to a B2CORE internal action:

PSP response statusB2CORE action
"inProgress" + deadline not exceededContinue polling (retry)
"inProgress" + deadline exceededMove to unexpected
"success"Move to success, stop polling. Save finalAmount and finalCurrencyCode
"failed"Move to failed, stop polling. Save reason
"unprocessable"Move to unexpected, stop polling. Save reason. Requires admin investigation
HTTP 5xx / timeout / parse errorTreat as an unexpected poll attempt. Continue polling if the deadline is not exceeded
HTTP 404 with X-Safe-To-Fail-After-SecondsContinue polling. After the indicated time plus a safety margin (5 minutes), if still 404, move to failed with reason "deposit redirect URL is expired"
HTTP 404 without the headerContinue polling. Wait for the deposit to appear on the PSP side or the sync deadline to be exceeded

Deposit status lifecycle

This section describes B2CORE deposit statuses. For the mapping of PSP response statuses to B2CORE statuses, see Start result mapping and Polling result mapping.

Full status diagram

flowchart TD
    created[created]
    post["POST /api/v1/deposits"]
    failed1[failed]
    inProgress1[in_progress]
    unexpected1[unexpected]
    poll["GET /api/v1/deposits/{externalID}<br/>(polling)"]
    retryIP["(in_progress)<br/>(retry)"]
    retryUX["(unexpected)<br/>(retry)"]
    success[success]
    failed2[failed]
    unprocessable["(unprocessable)<br/>(stop polling)"]
    inProgress2[in_progress]

    created --> post
    post --> failed1
    post --> inProgress1

    inProgress1 --> poll
    poll --> retryIP
    poll --> retryUX
    poll --> success
    poll --> failed2
    poll --> unprocessable

    retryIP --> inProgress2
    retryUX --> inProgress2
    unprocessable --> unexpected1
    unexpected1 -->|Manual recovery| inProgress2

Recovery from the unexpected status

An admin can perform these manual transitions via the LifecycleService:

TransitionWhen to use
unexpectedin_progressRetry polling (for example, after a PSP outage is resolved)
unexpectedsuccessOnly if the last poll attempt was unprocessable and the admin confirms success
unexpectedfailedThe admin confirms the deposit failed

Deposit status definitions

The following table defines each B2CORE deposit status:

StatusBusiness meaning
in_progressThe deposit is being processed. Covers all intermediate states: pending bank transfer, 3DS verification in progress, awaiting manual review on the PSP side, the client interacting with the PSP payment page, and similar. This is a non-terminal status — B2CORE continues polling.
successThe broker has received the client's money. The PSP has confirmed the funds were credited. The finalAmount and finalCurrencyCode fields reflect the actual amount and currency received, which may differ from the initial request due to fees or conversion.
failedThe deposit did not go through. The client did not lose any money, and the broker did not receive any funds. Examples: card declined, bank transfer rejected, user canceled on the PSP page, or redirect link expired.
unexpectedThe deposit could not be resolved automatically and requires manual admin action via B2CORE (see Recovery from the unexpected status); automatic polling has stopped. Entered on a deadline timeout while in_progress, or when the PSP reports unprocessable.

Deposit creation timing

PSPs follow one of two patterns for deposit creation:

PatternBehaviorPolling impact
Immediate creationThe PSP creates the deposit record on POST /api/v1/deposits.GET /api/v1/deposits/{externalID} returns a result immediately after start.
Deferred creationThe PSP creates the deposit record only after the end user completes the PSP payment page.GET /api/v1/deposits/{externalID} returns 404 Not Found until the user completes the page.

For deferred creation, the PSP should return the X-Safe-To-Fail-After-Seconds header with the 404 response. This header tells B2CORE how long the redirect link is valid. After redirect_time + header_value + safety_margin, if the deposit is still 404, B2CORE marks it as failed with reason "deposit redirect URL is expired".

If the header is absent, B2CORE continues polling until the deposit appears or the sync deadline (default 4 hours) is exceeded, at which point the deposit moves to unexpected.

Behavioral requirements

Distributed tracing

All HTTP requests from B2CORE to the PSP include standard tracing headers per the W3C Trace Context specification:

  • traceparent — contains the trace ID, parent span ID, and trace flags.
  • tracestate — vendor-specific trace data.

PSP implementations should propagate these headers to their downstream services for end-to-end observability.

PSP payment page currency lock

When the start deposit response includes a redirect to a PSP payment page:

  • The PSP page must not allow the end user to change the currencyCode to any analog, equivalent, or alternative currency.
  • The currency shown on the PSP page must match exactly what was sent in the start deposit request.
  • If the PSP page allows currency selection, the currency must be pre-selected and locked.

IP whitelisting recommendation

While not required by the API specification, PSP implementations are strongly recommended to configure IP whitelisting on their side. This provides an additional layer of security beyond JWT authentication, limiting API access to known B2CORE IP addresses.

To make the Canonical driver available in your environment, please reach out to your account manager. Kindly specify how exactly and for what purposes you plan to use the Canonical driver so we can arrange access accordingly.

Last updated on

On this page