CodeVASP
CodeVASP is one of Covalent's Travel Rule providers: VASPs in the CodeVASP network exchange IVMS101 data through CodeVASP's API. Covalent calls that API with signed REST requests, and encrypts the IVMS101 data for the receiving VASP.
Covalent sends outgoing transfers through CodeVASP, and answers CodeVASP's calls about incoming ones. Each authorization is decided within one request: CodeVASP answers verified or denied for an outgoing transfer, and Covalent does the same for an incoming one.
Provider Config
Section titled “Provider Config”| Field | Value |
|---|---|
| Name | codevasp |
| Display name | CodeVASP |
| Protocol | codevasp |
| Active | true |
| Discovery | Wallet discovery in the CodeVASP network |
| Transport | Signed REST requests with end-to-end encrypted IVMS101 |
Required Provider Fields
Section titled “Required Provider Fields”Save CodeVASP's credentials for each environment with PUT /api/v2/providers/codevasp, or in the dashboard under Travel Rule, Providers: a test key saves the test ones, a live key the live ones. All five fields are required.
| Field | Type | Value |
|---|---|---|
apiUrl |
text |
The CodeVASP API origin for the environment: https://, with no path, query or credentials. |
vaspEntityId |
text |
Your VASP entity ID in CodeVASP: letters, digits, _ and -. |
privateKey |
password |
Your Ed25519 key: the base64-encoded 32-byte seed from the CodeVASP dashboard. |
legalName |
text |
Your registered legal name. |
country |
text |
Your country of registration: two uppercase letters, such as KR. |
privateKey is the only secret. The API returns no values; the dashboard shows the other four. Saving checks only that each field holds a value: Covalent checks the formats above when it uses the credentials, in the connection test, a transfer or a CodeVASP call.
Covalent signs each request to CodeVASP with privateKey, names your entity as code:<vaspEntityId>, and encrypts and decrypts IVMS101 data with the same key. A request times out after 15 seconds and follows no redirects. legalName and country are your VASP in the IVMS101 data, as originating and as beneficiary VASP: CodeVASP transfers do not use your company profile.
curl -X PUT "$COVALENT_URL/api/v2/providers/codevasp" \ -H "x-api-key: $COVALENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "credentials": { "apiUrl": "https://api.codevasp.example", "vaspEntityId": "acme-exchange", "privateKey": "...", "legalName": "Acme Exchange Ltd", "country": "GB" } }'Enable CodeVASP with a live key (PATCH /api/v2/providers/codevasp): one switch for both environments. While it is disabled, Covalent routes nothing through it, its callbacks answer 503, and transaction reports and cancellations on its transfers are refused. See Providers and Integrations.
Capabilities
Section titled “Capabilities”| Capability | Value |
|---|---|
| Originator address | Required |
| Beneficiary address | Required |
| Beneficiary IVMS101 | Required: at least one person in beneficiary.beneficiaryPersons |
| Originator IVMS101 | The request's originator, not the customer's verified Travel Rule identity |
| Beneficiary VASP | Not required upfront: discovered from the address |
| Destination memo | Supported, sent as tag and as <address>:<memo> in account numbers |
| Wallet proof type | None |
Connection Test
Section titled “Connection Test”POST /api/v2/providers/codevasp/test reads the key's environment's credentials, then asks CodeVASP for its VASP directory (GET /v1/code/vasps) with a signed request. It succeeds when CodeVASP answers with the directory. A failed test's error names the problem: Invalid CodeVASP configuration: ... or Invalid CodeVASP key or encrypted payload encoding for a value Covalent cannot use, else CodeVASP request failed (<code>), where the code is CodeVASP's errorType, or HTTP_<status>, TRANSPORT_ERROR or INVALID_RESPONSE. The test does not check your callback paths.
Callbacks
Section titled “Callbacks”CodeVASP calls your company's host on one path per endpoint:
https://<your company's host>/api/system/trp/codevasp/<environment>/connections/<providerId>/<endpoint><environment> is test or live. Copy the eight paths, with their methods, from the provider's callbackPaths, which lists them for the key's environment once the provider has been saved, and register them with CodeVASP. See Callback Paths.
| Method | Endpoint | Covalent's answer |
|---|---|---|
GET |
v1/vasp/health |
200 {}. |
POST |
v1/beneficiary/VerifyAddress |
Whether the beneficiary account is the verified wallet of a customer with a verified Travel Rule identity, and, when persons are sent, whether they match it: valid, or invalid with a reasonType; with your vaspEntityId. Nothing is recorded. |
POST |
v1/beneficiary/transfer |
verified or denied: see Incoming Transfers. |
POST |
v1/beneficiary/transfer/txid |
Records the transaction (txid, vout) of a verified incoming transfer, which becomes accepted. |
PUT |
v1/vasp/transfer/status |
Records the originator's cancellation (status canceled): the incoming transfer is rejected, or failed once its transaction was reported, with reason ORIGINATOR_CANCELED. |
POST |
v1/vasp/transfer/status |
The status of your outgoing transfer to the caller: pending, processing once its transaction is reported, or canceled. |
POST |
v1/verification/tx |
valid when the transaction (txid, beneficiaryAddress) is your outgoing transfer to the caller, else invalid. |
PUT |
v1/verification/tx |
Given the caller's beneficiary data, that transaction's Travel Rule data: your originator's IVMS101 data, the amount, its USD value and isExceedingThreshold. Else result error, with NOT_FOUND_TXID or LACK_OF_INFORMATION. |
Verification
Section titled “Verification”Covalent does not verify a signature on CodeVASP's calls: their source address authenticates them. Covalent accepts a call only:
- from an address in the platform setting
CODEVASP_ALLOWED_IPS: IPv4 or IPv6 addresses or CIDR ranges, matched againstCF-Connecting-IP. It applies to both environments and every endpoint, health included. While it is empty, every CodeVASP call is refused with403; - for your CodeVASP provider, enabled, with credentials for the path's environment;
- with
X-Request-Originnaming the sending VASP, such ascode:<entity ID>, andX-Code-Req-PubKeyits Ed25519 public key in base64. Both are required except onv1/vasp/health. AnX-Code-Req-Remote-PubKey, when sent, must be your public key, the oneprivateKeygives; - when each
payloaddecrypts with your private key and the sender's public key (libsodiumcrypto_box). Thepayloadof an answer is encrypted for the sender's key.
A call reads and changes only its path's environment. Status and transaction questions are answered only about your outgoing transfers to the sender's entity. Refusals use CodeVASP's format, { "errorType": "<code>" }:
| Status | errorType |
When |
|---|---|---|
403 |
FORBIDDEN |
The source address is not allowed. |
413 |
MESSAGE_TOO_LARGE |
The body is over 256 KiB. |
422 |
MISSING_REQUIRED_MSG_FIELD |
The body is not JSON, or a field or a sender header is missing or not valid. |
422 |
INVALID_RECEIVER_PUBLIC_KEY |
X-Code-Req-Remote-PubKey is not your public key. |
503 |
VASP_BACKEND_INTERNAL_ERROR |
The provider is not yours, is disabled, or has no usable credentials for the environment; a payload does not decrypt; or processing failed. |
503 |
Another code | Such as DUPLICATE_TRANSFER_ID, REQUEST_IN_PROGRESS or UNKNOWN_TRANSFER_ID. |
Another method or path under /api/system/trp is 404 { "error": "Not found", "code": "NOT_FOUND" }. When your company's request budget, shared with /api/v2, is used up, a call is 429 RATE_LIMITED, with Retry-After.
Duplicates
Section titled “Duplicates”- An authorization request, or a
PUT v1/verification/tx, is recorded by environment, sender and CodeVASPtransferId. The same request again gets the first answer again, itspayloadencrypted for the sender's current key. The sametransferIdwith other content isDUPLICATE_TRANSFER_ID. A request still being processed isREQUEST_IN_PROGRESS; after five minutes it may be processed again. - A transaction report sent again with the same
txidandvoutanswersresultnormal; anothertxidanswersresulterror,TXID_ALREADY_EXISTS. A cancellation sent again answersnormal. - A transaction report or cancellation from another sender than the authorization's answers
resulterror,UNKNOWN_TRANSFER_ID.
Incoming Transfers
Section titled “Incoming Transfers”Covalent answers an authorization request (POST v1/beneficiary/transfer) within the request:
- It decrypts the IVMS101 data. The beneficiary needs one account number and at least one person, whatever the amount; the originator, one account number and valid persons; the originating VASP, a legal person.
- It looks for the account among your wallets in the environment, on a network Covalent supports for the asset: exactly one must match. The wallet must be verified, and its customer must have a verified Travel Rule identity.
- It compares the beneficiary persons with that identity: the first person, and each representative sent for a company. Names match ignoring case, spacing and punctuation, with given and family names in either order.
- It creates the incoming transfer, then screens it with your rules and screening integrations, each further person on either side included.
reasonType |
Why Covalent answers denied |
|---|---|
NOT_SUPPORTED_SYMBOL |
Covalent does not support the asset, or that network for it. |
NOT_FOUND_ADDRESS |
Not exactly one wallet has the account, or the request's address and tag differ from it. |
NOT_KYC_USER |
The wallet is not verified, or the customer's identity is not verified. |
SANCTION_LIST |
The customer's identity is blocked. |
INPUT_NAME_MISMATCHED |
The beneficiary persons do not match the identity. |
LACK_OF_INFORMATION |
Data is missing, or screening needs a review. |
UNKNOWN |
Screening rejected the transfer. |
A request denied before step 4 creates no transfer. After step 4, a verified answer leaves the transfer awaiting_counterparty, with compliance approved; a denied one makes it rejected, with the reasonType as rejection.reason. Its cascade.externalId is codevasp-inbound: followed by Covalent's ID for the request, and its originator.vaspId is the sender's X-Request-Origin.
The answer names your legalName and country as beneficiary VASP, and returns the IVMS101 data received, encrypted for the sender. It does not carry your customer's verified identity: Covalent checks the persons the originator sent against it.
An incoming CodeVASP transfer is never held for an officer: a verdict that needs review is answered denied, so accept and reject do not apply to it. After verified, the originator reports the transaction or cancels, as in Callbacks.
Outgoing Transfers
Section titled “Outgoing Transfers”POST /api/v2/transfers does not name a provider (see Transfers). CodeVASP can carry a transfer that has an originatorAddress and beneficiary persons. With "preferredProtocol": "codevasp":
- The create is refused with
400 VALIDATION_ERRORunless CodeVASP is enabled, has credentials for the key's environment, and can carry the transfer, as inPreferred protocol "codevasp" cannot handle this request: originatorAddress is required. - Covalent tries CodeVASP first, unless the beneficiary VASP was last reached through another provider: that one goes first.
- Another enabled provider may still carry the transfer when CodeVASP finds no VASP for the address, or when Covalent cannot build the CodeVASP request from the transfer's data.
- Once Covalent has reserved a CodeVASP
transferId, no other provider is tried.
VASP Discovery
Section titled “VASP Discovery”A transfer cannot name its beneficiary VASP. While CodeVASP is enabled with credentials for the environment, Covalent asks it who holds the beneficiary address of every outgoing transfer it routes, whatever preferredProtocol names. It submits the asset, network and address, followed by :<memo> with a destination memo, to POST /v2/code/VerifyAddress, and reads the result from GET /v2/code/VerifyAddress/<requestId>. The request ID is derived from the transfer and its destination: a restarted job reads the same search instead of starting another.
| CodeVASP's answer | Effect |
|---|---|
| One VASP found | That VASP, by its entity ID, is the beneficiary VASP for CodeVASP. |
NOT_FOUND_ADDRESS |
No VASP on CodeVASP: its attempt is vasp_not_found, and another provider may carry the transfer. |
Pending, 429, 5xx, no answer, or credentials Covalent cannot use |
The transfer job asks again at least 11 seconds later, for up to five minutes; then the transfer is failed, unless a provider tried before CodeVASP carries it. |
| More than one VASP, or any other answer or error | Routing stops: the transfer is failed, and no provider carries it. |
Authorization
Section titled “Authorization”Covalent reserves a CodeVASP transferId, a UUID, for the transfer. Then it:
- Checks that CodeVASP's directory lists the beneficiary VASP with health
up. - Values the amount in USD at Covalent's FX rate, and works out whether it reaches the Travel Rule threshold between your
countryand the beneficiary VASP's. - Sends the beneficiary VASP an address check (
POST /v1/code/VerifyAddress/<entity ID>). The answer must name the same VASP. - Sends the authorization request (
POST /v1/code/transfer/<entity ID>):transferId, asset, amount,tradePrice(the USD value) withtradeCurrencyUSD,isExceedingThreshold, address,tagand network, and the IVMS101 data, encrypted for the beneficiary VASP's current public key (GET /v1/code/Vasp/<entity ID>/pubkey).
The IVMS101 data is the request's originator and beneficiary, with originatorAddress and the beneficiary address as account numbers, and your legalName and country as originating VASP. Covalent sends each request once, and again only when CodeVASP refuses it with INVALID_RECEIVER_PUBLIC_KEY, after reading the key again.
| CodeVASP's answer | Transfer |
|---|---|
verified, with beneficiary IVMS101 for the transfer's address |
status.theirs is accepted. Covalent screens the beneficiary data: approved moves the transfer to awaiting_counterparty; otherwise it is held, and a rejection cancels it on CodeVASP (see Accept and Reject). |
denied, or the address is invalid |
rejected, with CodeVASP's reasonType as rejection.reason: UNKNOWN, or NOT_FOUND_ADDRESS for the address, when CodeVASP gives none. |
| An error, or an answer that does not match the request | failed, with the CodeVASP attempt submission_unknown. |
Once CodeVASP has answered, the transfer's beneficiary.vaspId is the beneficiary VASP's entity ID, and its cascade.externalId, like the externalId of its CodeVASP attempt, is the CodeVASP transferId. A transfer held for review resumes when its case is resolved as approved.
Accept and Reject
Section titled “Accept and Reject”Transaction reports and cancellations on a CodeVASP transfer use strict notification: Covalent tells CodeVASP first, and changes the transfer only once CodeVASP answers result normal for that transferId.
To complete an outgoing transfer that is awaiting_counterparty, with status.theirs accepted and compliance approved, send PATCH /api/v2/transfers/:id with action accept and the txHash (see Transfer Actions). Covalent reports the hash (POST /v1/code/transfer/<entity ID>/txid), then the transfer is completed. When CodeVASP refuses or cannot be reached, the action answers 400 INVALID_REQUEST with the reason in error, such as CodeVASP request failed (TRANSPORT_ERROR), and the transfer does not change. Covalent keeps a hash it has tried to report: a later accept must send the same txHash, or it is refused with CodeVASP request failed (TXID_ALREADY_EXISTS).
reject takes incoming transfers held for an officer's review, and outgoing ones held before any provider carries them. A CodeVASP transfer is neither, so reject answers 400 INVALID_REQUEST. Covalent cancels an outgoing transfer itself when screening rejects the beneficiary data: it sends PUT /v1/code/transfer/<entity ID>/status with status canceled and reasonType UNKNOWN, and the transfer is rejected once CodeVASP answers normal. Otherwise it stays held, with compliance rejected.
Known Limitations
Section titled “Known Limitations”- Source address only. Covalent authenticates CodeVASP's calls by their source address, not by a signature. Until the platform's
CODEVASP_ALLOWED_IPSlists CodeVASP's addresses, every call is refused. - No review of incoming transfers. Covalent decides an incoming transfer within CodeVASP's request: one that needs review is denied, and none can be accepted or rejected afterwards.
- No on-chain confirmation. Covalent does not follow CodeVASP transactions on chain. An outgoing transfer is
completedonce CodeVASP takes its hash; an incoming one staysacceptedonce its transaction is reported; Covalent's status answers to CodeVASP stop atprocessing. - No status polling. Covalent never asks CodeVASP for a transfer's status: it relies on the authorization's answer and on CodeVASP's calls.
- CodeVASP's search can stop routing. While CodeVASP is enabled with credentials for an environment, it searches for every outgoing transfer's beneficiary VASP. An error in that search, such as a refusal of your credentials, fails the transfer whichever provider could carry it; a search that never answers fails it after five minutes.
- Lost answers. When an attempt fails after its
transferIdis reserved, for example because CodeVASP's answer was lost, the transfer isfailed, the attempt issubmission_unknownand shows notransferId, and a retry does not send it again. Covalent has no reconciliation for it. - One output per destination.
v1/verification/txcannot tell apart several outputs to the same address in one transaction, and Covalent's transaction reports carry novout.