Salty IM Specification

Version Last Modified / Changelog
v1.5.1 2023-01-28
v1.5 2022-10-14
v1.4 2022-03-31
v1.3 2022-03-22
v1.2 2022-03-19
v1.1 2022-03-18


Salty IM is an open specification for a new Saltpack based e2e encrypted
messaging protocol and platform for secure communications with a focus on
privacy, security and being self-hosted.

You want to send a friend, colleague or loved one a message, what do you do?
You could send them an email, but email is inherently insecure and not very
private (your mail provider could read your emails for example), you could use any number of “free” services such as Meta’s Messenger or Apple’s iMessages, but can they really be trusted? What if they decide to change their terms of services? You could also use Delta.Chat, but is has several key weaknesses that lend itself to being only having opportunist encryption. You could even consider Signal, which arguably is the best choice these days except for its one drawback, it is a centralised service, admittedly run by a not-for-profit, but centralised nonetheless.

There are literally dozens and dozens of messaging system.

So what do you do? What if instead you could simply:


So Salty IM is a specification for the simplest possible IndieWeb-style and Twtxt inspired private messaging system. It uses standard Web technologies, meaning HTTP. It uses modern cryptography such as Saltpack and isn’t trying to be an all singing dancing blockchain, crypto-currency, federation complicated pile of over-engineered thing.

Salty is simple. Here’s how…




Alice and Bob can continue these exchanges from the last two steps repeatedly to engage in secure and private communications.



Salty IM uses Ed25519 Keys for encryption, decryption and signing.
All Cryptography is deferred to the Saltpack via the Go libraries.

A User creates an Ed25519 Private/Public key pair on first setup and stores the Private Key securely in Secure Storage. This Secure Storage is normally expected to be a System Keychain such as Keychain on macOS, Windows Credentials on Windows, and Secret Service on Linux and BSD.

Alternatively but not recommended the Private Key may be stored in the User’s
Home Directory, in a Browser’s Local Storage (at the User’s own risk) or other
Device-specific Secure or appropriate Storage.


A User must publish their Public Key along with an Endpoint for which to receive messages on.


Before requesting the well-known url, the client should do a SRV lookup for _salty._tcp.domain.tld:

1$ dig +short SRV _salty._tcp.domain.tld
20 0 443 other.domain.tld

If this results in a found SRV record it should be used in place of the domain.tld. In case of multiple records, sort by priority and randomise by weight within a priority. Then select the first result.

Domain registrars and domain hosting services and even DNS server software will different in how SRV records are configured, but the basics are the same and should be:

Well Known URI

This is published on a Well-Known URI that has the path:
/.well-known/salty/<hash>.json where:

The resource must be served on at least a TLSv1.2-capable web server or better and TLSv1.3 is encouraged where possible.

Cross-Origin Resource Sharing (CORS)

It is encouraged (but optional) that appropriate Cross-Origin Resource Sharing (CORS) headers be set-up on the server that services the Well-Known URI and Inbox endpoint. For example the following response headers are generally recommended (see example requests):

# GET requests must respond with:
# Access-Control-Allow-Headers: *
# Access-Control-Allow-Origin: *
$ curl -v -X GET "https://domain.tld/.well-known/salty/xxx.json"
< access-control-expose-headers: *
< access-control-allow-headers: *
< access-control-allow-origin: *

# OPTIONS reqeusts must respond with:
# Access-Control-Allow-Headers: *
# Access-Control-Allow-Origin: *
$ curl -v -X OPTIONS "https://domain.tld/.well-known/salty/xxx.json"
< access-control-expose-headers: *
< access-control-allow-headers: *
< access-control-allow-origin: *

Clients running in environments that implement Cross-Origin Resource Sharing policies (such as Web Browsers) should appropriately deal with the possibility that some Well-Known URI endpoints may not permit Cross-Origin requests and should implement measures accordingly (such as proxying the requests).

This is important so that discovery, submitting messages to inboxes and negotiating features are possible. Cross-Origin Resource Sharing is required for Salty IM to function correctly from clients in these environments (typically web browsers).

Directory Listing

It is recommended that Directory Listing be disabled for serving the
Well-Known URI resource(s) to prevent crawlers and bad actors from harvesting
Salty addresses.

Discovery Document

The contents of a User’s Well-Known Configuration file contains the following
JSON document:

    "endpoint": "https://domain.tld/path",
    "key": "xxx",

Example: has the following Well-Known URI:

This contains the following JSON document:

  "endpoint": "",
  "key": "kex1ekt5cru4vs42wnaxppkjn5pexmt2w6uxx9z2mz0fqeuc80e0g9gsggs8ah"

Example: has a SRV redirection

$ dig +short SRV
0 0 443
$ curl
  "endpoint": "",
  "key": "kex170sc6cd3x0vxr0mpve9dllzxwqlw3q7zpy48wahvs4u37u43uqzsxxlp39"


Messages are defined by the following simple TAB-delimited (\t) format ending with a NEWLINE (\n).

# <event>: [<args>]
2022-03-18T00:23:13Z    (alice@domain.tld) Hey bob 👋

Messages can also embed Markdown to enhance the User Experience (UX) and provide Users a way to share multimedia with each other as well as basic formatting.

The format in each message contains:


It is important that the time-stamp be in UTC so that Messages have a
consistent time and it is the responsibility of Clients to represent the time-stamp appropriately (for example in local time).

The Identity of the sender is simply the Sender’s user@domain.tld used as part of the Discovery process and bares no meaning to the User’s actual identity or in any way carries any personal identifiable information about the user (other than they are nick at domain.tld).

In addition a Message may contain one or more Commented lines that are ignored
by a Client for display purposes but are used for Additional features such as
“Read receipts”.


A Message may contain one or more Comment lines containing Events that affect the behaviour of Clients.

Comment lines must be ignored by Clients for display purposes.

The currently supported Events are:


Saltpack is used to encrypt the Message (once properly formatted) and the message is Signed and Encrypted (Sigcrypt) to all the intended recipients.


Once a Message has been Signed and Encrypted to all intended recipients, those recipients are looked up using the Discovery process. Then each message is sent to the recipient’s Endpoint(s).

At this point the exact serialisation of the Signed and Encrypted Message is not important, however it is recommended to use the Saltpack Armour encoding.

The Serialised Message is delivered to each recipient’s Endpoint via a simple
POST request. Example:

curl --data-binary "@./message.enc" https://domain.tld/user

The Endpoint must respond with a 202 Accepted on success or a 400 Bad Request on any error.

The Endpoint must be served on at least a TLSv1.2-capable web server or better and TLSv1.3 is encouraged where possible.

How a User deals with the delivered Message(s) is Client-dependent.
It is expected that a Client will connect to a User’s Endpoint, either request the contents of their messages or subscribe for real-time consumption.

A Client shall verify the Sender once the message has been decrypted and before displaying their contents, by making a request for the Sender’s Well-Known URI to ensure the received message’s signature matches the Sender’s key.