Outgoing transactions
Integrate outgoing Travel Rule transactions with CryptoSwift, including post- and pre-transaction patterns.
Use this guide to build outbound Travel Rule transaction flows. It covers the standard post-transaction pattern, the request-before-broadcast pre-transaction variant, and how to handle status updates.
Post-transaction flow
The following example initiates a new outgoing Travel Rule transaction in compliance with FATF Recommendation 16. In the post-transaction flow, the Travel Rule message is sent immediately after the on-chain transaction is completed, allowing the transaction hash to be included in the Travel Rule message payload.
This is the fastest way to become Travel Rule compliant because it requires minimal workflow changes. It is especially useful when your AML, KYT, and release controls already run outside CryptoSwift and you mainly need to add Travel Rule messaging without changing how withdrawals are approved.
First make sure you have your API key ready:
API_KEY='a70fcedf-416b-4f83-845c-a05aba0d7da4'
Then send a POST request to the CryptoSwift API with the relevant data:
curl --location --request POST 'https://api-dev.cryptoswift.eu/transactions' \
--header 'x-api-key: $API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"asset": "BTC",
"amount": "0.00341",
"blockchainInfo": {
"blockchain": "Bitcoin",
"transactionHash": "cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79",
"origin": "17SkEw2md5avVNyYgj6RiXuQKNwkXaxFyQ",
"destination": "1MLh2UVHgonJY4ZtsakoXtkcXDJ2EPU6RY",
"destinationType": "CUSTODIAL"
},
"vaspInfo": {
"beneficiaryVaspName": "SwiftExchange"
},
"originator": {
"type": "NATURAL",
"name": "Marwin Hillar",
"accountNumber": "04143282398",
"address": "Alexanderplatz 25, Berlin",
"country": "Germany"
},
"beneficiary": {
"type": "NATURAL",
"name": "Hanne Nikol",
"accountNumber": "1MLh2UVHgonJY4ZtsakoXtkcXDJ2EPU6RY"
}
}'
The request returns the full Travel Rule message payload, including the transaction UUID, so you can reconcile the payload later. This is useful if you need to patch the transaction data or track delivery statuses after submission.
Even in a post-transaction flow, the response still gives you useful information for downstream controls, including the Travel Rule message status, counterparty context, and any configured risk evaluation outputs. The key distinction is that the blockchain transaction already exists when the Travel Rule message is created.
Using the post-transaction flow is currently the easiest and fastest way to achieve Travel Rule compliance because you do not need complex logic for response handling, timeouts, or fallback mechanisms. If you later want to automate decisions before funds move, add a pre-transaction workflow and the Rule Engine only where needed.
See the Compliance Workflows section to find out more about the post-transaction flow.
Pre-transaction flow
If you need a decision before the actual on-chain settlement, create the Travel Rule message without the transaction hash and include blockchain context so the counterparty VASP understands what they are reviewing.
Pre-transaction workflows are not limited to waiting for a single counterparty confirmation. You can use the immediate response together with asynchronous updates to make a decision based on multiple variables, such as:
- counterparty response
- Travel Rule Risk Score
- transaction amount
- beneficiary VASP
- other variables evaluated by the Rule Engine
Possible outcomes include proceeding immediately, waiting for a defined period, routing the transaction for review, or blocking the transaction.
Make the transaction
Start by sending the Travel Rule data (note that the transaction hash is missing):
curl --location --request POST 'https://api-dev.cryptoswift.eu/transactions' \
--header 'x-api-key: $API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"asset": "BTC",
"amount": "0.00341",
"blockchainInfo": {
"blockchain": "Bitcoin",
"origin": "17SkEw2md5avVNyYgj6RiXuQKNwkXaxFyQ",
"destination": "1MLh2UVHgonJY4ZtsakoXtkcXDJ2EPU6RY",
"destinationType": "CUSTODIAL"
},
"vaspInfo": {
"beneficiaryVaspName": "SwiftExchange"
},
"originator": {
"type": "NATURAL",
"name": "Marwin Hillar",
"accountNumber": "04143282398",
"address": "Alexanderplatz 25, Berlin",
"country": "Germany"
},
"beneficiary": {
"type": "NATURAL",
"name": "Hanne Nikol",
"accountNumber": "1MLh2UVHgonJY4ZtsakoXtkcXDJ2EPU6RY"
}
}'
Evaluate the synchronous response first
After sending the Travel Rule data, you will receive a response with the full Travel Rule message payload, including the status of the transaction. In most cases this will be "PENDING" or "DELIVERED", indicating whether CryptoSwift was able to identify the beneficiary VASP and deliver the message in real time.
Treat this synchronous response as the first decision point in your workflow. It can provide multiple data points you can use for decisioning, including:
statusriskScoreamount,amountUsd, andamountEurwarnings, if presentruleEngine, if Rule Engine policies have been defined for your workflow
This gives you two implementation options:
- Build the decision logic in your own backend using the returned fields.
- Use the
ruleEngineoutput as the decision result for the next step.
If your team configures automated policies in the Client Dashboard, the Rule Engine combines the available signals and returns a decision your application can apply directly. This simplifies the developer implementation because compliance teams can update the decision logic in real time without developer effort.
For example, you could PROCEED with the on-chain transaction for low-amount, low-risk transactions without waiting for Travel Rule message delivery. See the pre-transaction workflow examples for example policy patterns. See Travel Rule Risk Score for how the score is determined. For the full response schema, see the Create Transaction API docs.
Response fields for pre-transaction decisioning
The Create Transaction response includes both a riskScore object and a ruleEngine object as part of the full Travel Rule message payload. These are the most important response sections for pre-transaction workflow automation.
The same payload sections are also included in:
- the Create Transaction response
- the Get Transaction by ID response payload
- transaction webhook notification payloads
riskScore
Overall risk score and severity of the transaction.
originatorRiskScoreOverall numerical (0 - 100) risk score of the originator of the transaction. Higher values mean higher risk.originatorRiskSeverityEnum:"low""medium""high"Overall risk severity of the originator of the transaction.beneficiaryRiskScoreOverall numerical (0 - 100) risk score of the beneficiary of the transaction. Higher values mean higher risk.beneficiaryRiskSeverityEnum:"low""medium""high"Overall risk severity of the beneficiary of the transaction.
ruleEngine
Rule engine evaluation result for this Travel Rule message.
decisionString ornullEnum:"PROCEED""WAIT""REVIEW""BLOCK"Final decision from the matched published rule.nullwhen no published rules exist.ruleEngineIdRequired string<uuid>Rule engine ID used for evaluation.ruleIdString ornullMatched published rule ID.nullwhen no published rules exist or no explicit rule matched.ruleNameString ornullMatched published rule name.conditionsObject ornullFull matched published rule condition info.ruleEngineVersionRequired number Rule engine version used for evaluation.0means no published rules exist yet.scopeRequired string Enum:"OUTGOING""INCOMING"Rule engine scope used for evaluation.waitForSecondsNumber ornullWAITduration in seconds whendecision = "WAIT".onTimeoutDecisionString ornullEnum:"PROCEED""REVIEW""BLOCK"Decision to apply whenWAITtimes out.
Example excerpt from the full Travel Rule message payload:
{
...
"riskScore": {
"originatorRiskScore": 0,
"originatorRiskSeverity": "low",
"beneficiaryRiskScore": 21,
"beneficiaryRiskSeverity": "low"
},
"ruleEngine": {
"decision": "PROCEED",
"ruleEngineId": "6292e827-ce05-4cae-a3c8-43e1fb073e1e",
"ruleId": "bcaab652-2fc8-434f-afe3-47a339e55b95",
"ruleName": "Proceed with low amount and delivered",
"conditions": {
"all": [
{
"field": "riskSeverity",
"value": "LOW",
"operator": "EQ"
},
{
"field": "amount",
"value": 1000,
"currency": "EUR",
"operator": "LT"
}
],
"any": []
},
"ruleEngineVersion": 2,
"scope": "OUTGOING",
"waitForSeconds": null,
"onTimeoutDecision": null
}
...
}
See the Create Transaction API docs for the full schema and latest field definitions.
Process asynchronous updates as well
If the synchronous response is not enough to proceed with the on-chain transaction, continue processing asynchronous updates through webhook notifications or polling. These updates usually include Travel Rule message status changes, and in some cases can also include updated risk score information if the risk score was not available immediately at message creation time.
The status can be:
"DELIVERED"- for previously pending messages, indicating CryptoSwift was able to identify and deliver the message. This does not confirm that the beneficiary VASP has responded."CONFIRMED"- the beneficiary VASP confirmed the message, meaning the provided data is correct."DECLINED"- the beneficiary VASP declined the message. ThestatusReasoningfield provides a mandatory free text explanation."FAILED"- the system encountered an error.
Set a timeout for these asynchronous updates. You can manage that timeout either:
- through the
ruleEngineconfiguration, so compliance teams control the waiting logic without developer changes - in your own backend logic, if you prefer to keep that orchestration internally
If updates arrive before the timeout, calculate the next step based on the new data. If the timeout arrives first, fall back to the default action defined by your policy. Depending on your operating model, that fallback could be REVIEW, but in some cases it may also be PROCEED.
This is where the Rule Engine is especially useful: developers do not need to hard-code every branch of the decision tree, while compliance teams can keep the logic aligned with regulatory and AML policy changes.
Make the on-chain payment
Proceed with the on-chain transfer when your workflow outcome allows it. For some teams that means waiting for "CONFIRMED". For others it means applying a rule-based decision that considers risk score, amount, or counterparty profile before broadcasting funds.
In real-world scenarios, getting an immediate "CONFIRMED" status update from the counterparty VASP may take longer than is reasonable to wait before executing the on-chain transaction. Also, many VASPs still do not use a Travel Rule solution or use one that is not connected to CryptoSwift's network. For a pre-transaction flow, always set up a fallback mechanism, for example, wait for a predefined amount of time before falling back to the post-transaction flow or another approach that aligns with your internal AML guidelines.
Patch the transaction hash
After the blockchain transfer completes, update the Travel Rule record so both VASPs can reconcile the message with the on-chain settlement.
curl --request PATCH "https://api-dev.cryptoswift.eu/transactions/{transactionId}" \
--header 'x-api-key: $API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"blockchainInfo": {
"transactionHash": "cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79"
}
}'
You can include any additional data fields that you want to update with the PATCH call.
See the Compliance Workflows section to find out more about the pre-transaction flow, including fallback scenarios and multi-signal decisioning with the Rule Engine.
Key data points explained
To begin with, specify the asset (for example, BTC or ETH) and the amount transferred in the transaction.
In the blockchainInfo and vaspInfo sections, include the essential data needed for us to deliver the Travel Rule message to the beneficiary VASP:
- blockchain: The blockchain. Leave it blank if your desired blockchain is not in the list in our API.
- transactionHash: The unique identifier of the on-chain transaction.
- origin: The blockchain wallet address from which the transaction is initiated.
- destination: The blockchain wallet address to which the transaction is being sent.
- beneficiaryVaspName and beneficiaryVaspEmail: This information helps us deliver Travel Rule messages, especially if the destination address is unknown to CryptoSwift or belongs to a VASP outside our network. There are usually two options for obtaining this data:
- Ask your customer which crypto service provider the beneficiary is using. Use the Entities endpoint to build an auto-complete of known VASPs. You can fall back to free text entry if the VASP is not on the list.
- Use blockchain analytics tools to obtain the VASP name associated with the wallet address.
For more guidance on ensuring high-quality data, see Improving data quality.
The originator and beneficiary sections should include all required data for both parties. The accountNumber for both the originator and beneficiary serves as the internal identifier used by the VASP to recognize the user. This could be the wallet address itself or another available account identifier.
For detailed requirements regarding originator and beneficiary information mandated by regulations, refer to our blog post on data requirements for the Travel Rule. If you do not have all the necessary data available at the moment, you can still start the integration process. For guidance, check our blog post on iterative integration.
Warnings
When you fetch your Travel Rule messages list or details, outgoing messages can include a warnings list in the response if we detect possible data inconsistencies. See the warnings field in the Transactions API response for details. The possible values are:
- WALLET_POSSIBLY_CUSTODIAL: The message states the destination wallet is non-custodial (self-hosted), but our records suggest it might be custodial.
- WALLET_POSSIBLY_NON_CUSTODIAL: The message states the destination wallet is custodial, but our records suggest it might be self-hosted instead.
- VASP_POSSIBLY_MISMATCH: A VASP name was provided in the payload, but our records suggest the name might be incorrect.
Testing your integration
In the development environment, we are not transferring live Travel Rule messages between real VASPs. Still, it is important to be able to test different statuses (PENDING, DELIVERED) and status updates (CONFIRMED, DECLINED) for outgoing Travel Rule messages. For this, we have a list of known wallet addresses available in the test environment that you can use for development and testing. See Testing webhook notifications for details.
Next steps
- Post-transaction workflow
- Pre-transaction workflow
- Travel Rule Risk Score
- Rule Engine
- Incoming transactions
- Webhooks