Skip to content

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.

Always double-check you are using the correct environment when integrating. Using the wrong base URL or API key will result in authentication errors.

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.

Post-transaction workflow explained

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:

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:

  • status
  • riskScore
  • amount, amountUsd, and amountEur
  • warnings, if present
  • ruleEngine, 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 ruleEngine output 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.

  • originatorRiskScore Overall numerical (0 - 100) risk score of the originator of the transaction. Higher values mean higher risk.
  • originatorRiskSeverity Enum: "low" "medium" "high" Overall risk severity of the originator of the transaction.
  • beneficiaryRiskScore Overall numerical (0 - 100) risk score of the beneficiary of the transaction. Higher values mean higher risk.
  • beneficiaryRiskSeverity Enum: "low" "medium" "high" Overall risk severity of the beneficiary of the transaction.

ruleEngine

Rule engine evaluation result for this Travel Rule message.

  • decision String or null Enum: "PROCEED" "WAIT" "REVIEW" "BLOCK" Final decision from the matched published rule. null when no published rules exist.
  • ruleEngineId Required string <uuid> Rule engine ID used for evaluation.
  • ruleId String or null Matched published rule ID. null when no published rules exist or no explicit rule matched.
  • ruleName String or null Matched published rule name.
  • conditions Object or null Full matched published rule condition info.
  • ruleEngineVersion Required number Rule engine version used for evaluation. 0 means no published rules exist yet.
  • scope Required string Enum: "OUTGOING" "INCOMING" Rule engine scope used for evaluation.
  • waitForSeconds Number or null WAIT duration in seconds when decision = "WAIT".
  • onTimeoutDecision String or null Enum: "PROCEED" "REVIEW" "BLOCK" Decision to apply when WAIT times 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. The statusReasoning field 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 ruleEngine configuration, 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.

Pre-transaction workflow explained

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:
    1. 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.
    2. 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