Complying with Anti-Money Laundering (AML) laws requires financial institutions (FIs) to know not
only who their customers are sending money to but who their customers are receiving money from. In
some jurisdictions banks are able to trust the AML procedures of other licensed banks. In other
jurisdictions each bank must do its own sanction checking of both the sender and the receiver. The
Compliance Protocol handles all these scenarios.

The Compliance Protocol is an optional, additional protocol to adhere to
after federation that aids organizations in sharing AML information.

This section specifies standard customer information fields that are exchanged between FIs
via the Compliance Protocol. The most common fields are:
• First Name
• Last Name
• Date of Birth
• Physical Address


The standard process for financial institutions to interact with each other via the Compliance
Protocol is as follows:

  1. Each organization that wishes to receive funds and participate in the compliance protocol
    creates an AUTH_SERVER and adds its location (URL) to the institution’s kuknos.toml.
  2. The sending FI contacts the receiving FI’s AUTH_SERVER providing information on the sender,
    whether it needs AML information on the recipient, the unsigned transaction, and any additional
  3. The receiving FI responds with whether they’re willing to send AML information on the recipient
    and whether they’ll accept the payment, along with additional information pertaining to their
    response (such as sending along the AML information of the recipient).
    Anchors are free create to implement their own web service that implements the compliance protocol.



The AUTH_SERVER provides a single web endpoint where a compliance request can be sent. The URL is
up to anchors and where anchors host this service, but a simple example would be https://[HOSTNAME]/aml. Once established they’ll need to add this URL to their kuknos.toml under the AUTH_SERVER key.

Sending A Request

To send a compliance request to the receiving financial institution, submit an HTTP POST request to the receiving institution’s AUTH_SERVER with the following specification:

  • Content-Type should equal application/x-www-form-urlencoded
  • The body of the request should be data=<data value>&sig=<sig value>

Request Components

data is a block of JSON that contains the following fields:

Name Data Type Description
sender string The payment address of the customer that is initiating the send. Ex. bob*
need_info boolean If the caller needs the recipient’s AML info in order to send the payment.
tx string: base64 encoded xdr.Transaction The transaction that the sender would like to send in XDR format. This transaction is unsigned and its sequence number should be equal ۰.
attachment string The full text of the attachment. The hash of this attachment is included as a memo in the transaction. The attachment field follows the Kuknos Attachment Convention and should contain at least enough information for the sender to allow the receiving FI to do their sanction check.

sig is the signature of the data block made by the sending FI. The receiving institution should
check that this signature is valid against the public signature key that is posted in the sending
FI’s kuknos.toml (SIGNING_KEY field).

Example Request

Example request body (please note it contains both parameters data and sig):

After decoding the data parameter it has a following form:

  "sender": "aldi*",
  "need_info": true,
  "attachment": "{\"nonce\":\"1488805458327055805\",\"transaction\":{\"sender_info\":{\"address\":\"678 Mission St\",\"city\":\"San Francisco\",\"country\":\"US\",\"first_name\":\"Aldi\",\"last_name\":\"Dobbs\"},\"route\":\"1\",\"note\":\"\",\"extra\":\"\"},\"operations\":null}"

A few things to note:

  • memo value of tx is a SHA256 hash of the attachment.
  • nonce is unique to each individual transaction. When a transaction on the receiving end is
    pending, the same nonce should be sent to fetch updates about the pending transaction

Server Response

The receiving institution’s AUTH_SERVER will return a JSON object with the following fields:

Name Data Type Description
info_status ok, denied, pending If the receiving institution is willing to share AML information or not.
tx_status ok, denied, pending If the receiving institution is willing to accept this transaction.
dest_info string Marshalled JSON of the recipient’s AML information. (only present if info_status is ok)
error string Error message (only present if info_status or tx_status is error or for internal server errors)
pending integer Estimated number of seconds till the sender can check back for a change in status. The sender should just resubmit this request after the given number of seconds. (only present if info_status or tx_status is pending)

HTTP status code must be equal:

  • ۲۰۰ OK if both info_status and tx_status are ok.
  • ۲۰۲ Accepted if both statuses are pending or one is pending and second ok.
  • ۴۰۰ Bad Request if data sent by the sender is invalid.
  • ۴۰۳ Forbidden if any of info_status and tx_status are denied.
  • ۵۰۰ Internal Server Error for server side errors.


۲۰۰ OK
    "info_status": "ok",
    "tx_status": "ok",
    "dest_info": "{\"name\": \"John Doe\"}"

۲۰۲ Accepted
    "info_status": "ok",
    "tx_status": "pending",
    "dest_info": "{\"name\": \"John Doe\"}",
    "pending": 3600

۴۰۰ Bad Request
    "info_status": "ok",
    "tx_status": "error",
    "error": "Invalid country code."

۴۰۳ Forbidden
    "info_status": "deny",
    "tx_status": "ok"

۵۰۰ Internal Server Error
    "error": "Internal server error"

Putting It All Together

In this example, Aldi (aldi* wants to send to Bogart (bogart*
If you haven’t read up on Federation yet, we’d suggest you start there

۱) BankSender fetches BankReceiver’s kuknos.toml file

This is done to get BankReceiver’s AUTH_SERVER, FEDERATION_SERVER, and other important
information for BankSender to interact with BankReceiver.

BankReceiver’s kuknos.toml should be hosted at

۲) BankSender gets the routing info for Bogart so it can build the transaction

This is done by asking BankReceiver’s federation server to resolve bogart*

BankSender sends an HTTP GET request to [FEDERATION_SERVER]?type=name&q=bogart*

See Federation section for a complete description. The returned fields of
interest here are:

  • Kuknos AccountID of Bogart’s FI (BankReceiver).
  • Bogart’s routing info at BankReceiver.

Example federation response:

  "kuknos_address": "bogart*",
  "memo_type": "id",
  "memo": 1

۳) BankSender makes the Auth (Compliance) Request to BankReceiver

This request will ask BankReceiver for Bogart’s AML info and for permission to send a transaction
from Aldi to Bogart.

BankSender -> AUTH_SERVER

Example request body (it contains both data and sig):

After decoding the data parameter it has a following form:

  "sender": "aldi*",
  "need_info": true,
  "attachment": "{\"nonce\":\"1488805458327055805\",\"transaction\":{\"sender_info\":{\"address\":\"678 Mission St\",\"city\":\"San Francisco\",\"country\":\"US\",\"first_name\":\"Aldi\",\"last_name\":\"Dobbs\"},\"route\":\"1\",\"note\":\"\",\"extra\":\"\"},\"operations\":null}"

Please note that the memo value of tx is the SHA256 hash of the attachment, and the payment
destination is what is returned by the federation server. You can check the transaction above using
the XDR Viewer.

۴) BankReceiver handles the Auth request and sends a response to BankSender

  1. BankReceiver gets sender domain by spliting the sender address (aldi* in 2 parts: aldi and
  2. BankReceiver also fetches BandSender’s kuknos.toml file at This is done to get BankSender’s
  3. BankReceiver verifies the signature (the sig field) on the Auth Request was signed with
    BankSender’s SIGNING_KEY.
  4. BankReceiver does its sanction check on Aldi (the sender) utilizing its own AML/KYC
    infrastructure, defined outside of the Kuknos protocol. This determines the value of
    tx_status in the response.
  5. BankReceiver makes the decision to reveal the AML info of Bogart if any of the following
    criteria apply:

    • Bogart has made their info public.
    • Bogart has allowed BankSender for transactions.
    • Bogart has allowed Aldi for transactions.
    • BankReceiver has allowed BankSender for transactions.
  6. If none of the above criteria are met, BankReceiver should ask Bogart if he wants to reveal this
    info to BankSender and accept this payment. In this case BankReceiver will return info_status: "pending" in the Auth request reply to give Bogart time to accept the payment or not.
  7. If BankReceiver determines it can share the AML info with BankSender, it sends this info in
    dest_info field with the reply.

Example Response:

    "info_status": "ok",
    "tx_status": "ok",
    "dest_info": "{\"name\": \"Bogart Doe\"}",

۵) BankSender handles the reply from the Auth request

If the call to the AUTH_SERVER returned pending, BankSender must resubmit the request again after
the estimated number of seconds in the response.

۶) BankSender does AML checks on the receiver (Bogart)

Once BankSender has been given the dest_info from BankReceiver, BankSender does the sanction
check using Bogart’s AML info. If the AML/KYC check passes, BankSender signs and submits the
transaction to the Kuknos network.

۷) BankReceiver handles the incoming payment.

  • BankReceiver checks the transaction hash against a cache it has or redoes the sanction check on the sender
    (it’s up to the receiving FI to implement any caching for performance).
  • BankReceiver credits Bogart’s account with the amount sent or sends the transaction back.