Skip to main content
This guide is for companies that want their own users to deposit, register bank accounts and withdraw from the company’s WhatsApp line. You build and run the WhatsApp bot; Kravata provides these endpoints and keeps the user’s OTP and PIN checks. Your WhatsApp server can act on behalf of one end user at a time, with that user’s own session. Kravata never trusts the server to say who the user is:
  • Your server only says which WhatsApp number is writing (waId). Kravata finds the user with that phone in your company and sends an OTP to the user’s own phone (SMS) and email. Only the code the user types back opens a session.
  • The PIN never goes through your server. The user types it in a WhatsApp Flow whose data endpoint is Kravata.
  • With the user’s token, your server calls the same endpoints as the Kravata widget, so the same rules apply: your company’s OTP and PIN settings, KYC, tiers and limits.
In production, the channel token and session calls go through the partners domain and need the same client certificate as the rest of your partner API: see Production access. The calls made with the user token (PIN, deposit, accounts, withdrawal) go to api-kore, like the Kravata widget. The examples below use the test domain.
The channel is enabled per company. Ask your Kravata point of contact to enable it and to issue your channel credential.

Credentials and tokens

You work with two kinds of token:
Send the user token in both headers on every user call: Authorization: Bearer <token> and Cookie: accessToken=<token>. Some routes check one header at the gateway and some check the other.

1. Open a session for the user

1

Request the OTP

When a number writes to you and you have no valid session for it, call POST /api/v1/channel/whatsapp/session/request with your channel token. waId is the number as WhatsApp sends it (country code, no +).
Tell the user that a code was sent by SMS and email. Kravata never sends this code through WhatsApp.
2

Verify the code the user types

Store the tokens for that waId. A new session for a number ends the previous one.
3

Keep the session alive

Before the access token expires, call POST /api/v1/channel/whatsapp/session/refresh with {"waId", "refreshToken"}. A session can be refreshed for at most 7 days after it was opened; after that, repeat step 1.
4

End the session

When the user leaves (for example, writes “salir”), call POST /api/v1/channel/whatsapp/session/revoke with {"waId"}. It ends every channel session of that number.
Errors of these endpoints have the shape {"error": {"code": "...", "message": "..."}}:

2. Registration of new users

To be defined. When session/request answers USER_NOT_FOUND, the user must register and complete KYC outside the chat. The registration link for this channel is still being defined with each company; today users are registered through your own integration (Users).

3. Terms and conditions

Users accept Kravata’s terms and data policy on Kravata’s terms page. Your integration gets the link with POST /api/v1/client/terms-acceptance/generate-link ({"clientId", "userId"}, with your partner API token) and sends it to the user.

4. PIN setup

The PIN is created and recovered only in the Kravata widget, never in the chat. With the user token you can check whether the user already has one:
If your company requires the PIN and pinSet is false, send the user to the widget to create it.

5. Deposit

1

Get the user's wallet

GET /api/v1/custodian/kravata-wallets returns the user’s wallets (items). Use items[0].walletId as accountId in the deposit; its balances show the user’s balance.
2

(PSE) Get the banks

GET /api/v1/banks/get-banks-cashin returns [{"id", "name", "achcode", "status", ...}].
3

Create the deposit

bankCode is the bank’s achcode. With executionMode: "NORMAL", amount is the COP amount the user pays; with "INVERSE", it is the amount of the wallet’s token the user wants to receive.The response is {"operation": {...}, "tierEscalation": null, ...}. For PSE, send the user operation.depositInstructions.pseLink. If tierEscalation is present and operation is null, the user must complete a tier upgrade first (tierEscalation.actionType / actionPayload).

6. Register a bank account

1

Get the banks

GET /api/v1/banks/get-banks returns [{"id", "code", "name", "status"}].
2

Confirm with the PIN (if your company requires it)

See PIN confirmation with purpose: "account_creation".
3

Create the account

Use the same user token within 120 seconds of the PIN confirmation.
type is SAVING or CHECKING. For a third party’s account use accountOwnerType: "PERSON" with holderIdentificationType, holderIdentificationNro, holderFirstName and holderFirstSurname (or "COMPANY" with holderBusinessName). For a Bre-B key: {"name", "type": "SAVING", "number": "<key>", "currency": "COP", "paymentMethod": "BREB"}.GET /api/v1/accounts lists the user’s accounts (items). Editing an account (PATCH /api/v1/accounts/{id}) also asks for the PIN, with purpose: "account_update".

7. Withdrawal

1

Choose the destination account

GET /api/v1/accounts → the id of the account.
2

Confirm with the PIN (if your company requires it)

See PIN confirmation with purpose: "withdrawal". Each withdrawal needs its own confirmation.
3

Create the withdrawal

With executionMode: "NORMAL", amount is the amount of the wallet’s token (for example USDC) the user sells; with "INVERSE", it is the COP amount the user wants to receive. Use paymentMethod: "BREB" for a Bre-B account.The response has the same shape as the deposit; operation.prefix + operation.numberCode identify the withdrawal for the user.

Operation OTP in the channel

If your company requires an OTP for deposits or withdrawals, the OTP the user typed to open the channel session covers it while the user is active: their last OTP or PIN in that session must be at most 30 minutes old. Your server’s own calls (refresh, reads) don’t count as activity. After 30 minutes without activity, deposits and withdrawals are rejected with 403: open a new session (section 1) and retry.

PIN confirmation

When your company requires the PIN for an operation, the user confirms it in a WhatsApp Flow whose data endpoint is Kravata. Your server never receives the PIN.
1

Create the PIN challenge

With the user token, call POST /api/v1/pin/challenge. summary is the text the user sees in the Flow (up to 160 characters). purpose is withdrawal, account_creation or account_update.
If the user has no PIN, the answer is 404: send them to the widget to create it.
2

Send the WhatsApp Flow

Send your PIN Flow with flow_token = flowToken. Its data endpoint is POST /api/v1/channel/whatsapp/flows/pin/{clientId} (your clientId), on https://api-kore.kravata.co in production: Meta calls it, so it doesn’t use mTLS.
3

Wait for the Flow to finish

When the PIN is right, the Flow ends with pin_validated: true. Then send the operation with the same user token within 120 seconds. A confirmation authorizes one operation only.
Flow contract. Build your Flow with:
  • One screen with id PIN. Kravata sends it data.summary and, after a wrong PIN, data.error_message (for example “PIN incorrecto. Te quedan 4 intento(s).”).
  • A pin field submitted with data_exchange.
  • Completion on SUCCESS with params: flow_token, pin_validated and purpose.
Keys. Every WhatsApp Business Account uses its own key pair for Flows. Kravata keeps the private key and gives you the public key to upload to your WABA; you give Kravata the app secret Meta uses to sign the calls to the data endpoint. Kravata answers 432 to a bad signature, 421 when it can’t decrypt (Meta then re-fetches the public key) and 427 when the flow_token is unknown, used or expired.

When an operation is rejected

Deposits, withdrawals and account changes rejected by the gateway answer 403 without a reason. Check, in this order:
  1. The PIN confirmation was done with this same user token, less than 120 seconds ago, and was not already used.
  2. The user has a PIN (GET /api/v1/pin/status).
  3. The user was active in the last 30 minutes (operation OTP); otherwise open a new session.