Openmsg Technical Details
Openmsg Protocol
Version: 1.0
Status: Draft
Author: S. Walker
Date: 18 June 2025

1. Abstract
Openmsg is a decentralized, cross-platform messaging protocol designed to eliminate spam and enforce explicit trust-based communication between users. It uses one-time pass codes for connection establishment and mutual authentication, with end-to-end encryption and anti-spoofing verification built-in.

This document sets out the Openmsg protocol for connecting two users on different or on the same server (the initial handshake) and for sending a message between two connected users. It details the process and workflow, the format of data, the type of encryption and hashing used, what checks need to be performed and where certain key files must be stored on a domain.
2. Introduction
The core of Openmsg is permission-based messaging. One user cannot connect with another without explicit permission via a one-time pass code. After the connection (handshake) is made, the two users can message each other. For example:
If User A wants to message User B, User A needs not just User B’s address but also a one-time pass code that User B provides. Without a valid pass code, the connection attempt is silently rejected — no spam, not even spam requests.

A handshake securely exchanges auth codes and encryption keys. After that, messages are encrypted, timestamped, and hashed using the shared auth code. A callback is made to the domain in the senders address — ensuring the message was really sent from there.
The design prevents message spoofing, replay attacks, and the misuse of leaked accounts and auth codes.
3. Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHOULD", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Node: A server implementing the Openmsg protocol. A Node is represented and located by a domain name.
Provider: Another term for a Node. The provider provides the service to its Users.
User: An individual who uses a Node to send or receive messages.
Address: A unique identifier in the format <UserID>*<Domain>.
UserID: A numeric identifier of a specific User on a Node.
Handshake: The initial mutual verification process that establishes trust and a connection between two users.
Connection: Two connected Users.
Pass Code: A one-time-use code, with an expiry, issued to initiate a connection.
  • Active Pass Code: A Pass Code that has not been used and has not expired.
  • Expired Pass Code: A Pass Code that has either been used or had expired.
Ident Code: A private token used to identify a connection.
Auth Code: A private token used to validate authenticated communication between two users.
Message Package: The encrypted message and nonce, base64-encoded.
Message Bundle: The Message Package, the Auth Code, a random salt, and the message timestamp.
4. Data Element Formats
Pass Code
A Pass Code MUST be a string of 6 numbers. It MUST be randomly generated, using a truly random method, when a User requests a Pass Code. A Pass Code MUST be generated every time and NEVER reused. It SHOULD NOT be assumed that Pass Codes will be unique across all Openmsg Users. However a Pass Code MUST be unique to that User; it MUST be different to any and all other Active Pass Codes belonging to that User. It SHOULD be different to any other Expired Pass Codes belonging to that User of which expired within a certain timeframe. A Pass Code MUST be set to expire after it's creation. The expiry time SHOULD be 1 hour after it's creation.
Ident Code
The Ident Code MUST be a randomly ganerated string containing uniformly selected random bytes, 32 bytes in length. It MUST then be converted from binary to hexadecimal. The output MUST be an ASCII string containing the hexadecimal representation of the random bytes. The Ident Code MUST be unique per Node.
Auth Code
The Auth Code MUST be a randomly ganerated string containing uniformly selected random bytes, 32 bytes in length. It MUST then be converted from binary to hexadecimal. The output MUST be an ASCII string containing the hexadecimal representation of the random bytes. The Auth Code MUST be unique per Node.
Message Encryption
Message Encryption MUST use Libsodium (Sodium). For more details on the format of all the componants, see the Main Section "Message Encryption".
Message Encryption Key
Message Encryption Key MUST use Libsodium (Sodium)'s Keygen. It MUST be 32 bytes in length. It MUST then be converted from binary to hexadecimal using Sodium's binary to hex function. The output MUST be a string containing the hexadecimal representation of the random bytes. The hexadecimal string will be stored for later use. It will be converted back to binary for encryption.
The Message Encryption Nonce MUST use Libsodium (Sodium)'s Nonce creation function from random bytes. It MUST be 32 bytes in length. For storing in your database for later verification, it MUST then be converted to base64 encoding using Sodium's binary to base64 function. The URL SAFE NO PADDING flag MUST be set. The unencoded binary version is used to encrypt the message.
Message Encryption
The message MUST be encrypted using Sodium's SECRETBOX function, using the binary Message Encryption Nonce, and the binary Message Encryption Key
Message Package
The Message Package MUST be a string concatenation of MessageNonce + EncryptedMessage in that order, with no extra characters or spaces added. This string MUST then be Base64 encoded using Sodium's binary to base64 function. The URL SAFE NO PADDING flag MUST be set.
Message Hash Salt
The "Hash Salt" MUST be a randomly ganerated string containing uniformly selected random bytes, 16 bytes in length. A new Salt MUST be generated for each Hash. It MUST then be converted from binary to hexadecimal. The output MUST be an ASCII string containing the hexadecimal representation of the random bytes. A Salt MUST NOT be reused.
Message Timestamp
The timestamp of the creation of the Encrypted Message and Hash. This MUST be a string of the current numberical Unix Timestamp; the number of seconds from the Unix Epoch on January 1st, 1970 at UTC. The timestamp MUST be recalculated for each message.
Message Bundle
The Message Bundle MUST be a string concatenation of Message Package + Auth Code + Hash Salt + Message Timestamp in that order, with no extra characters or spaces added.
Message Hash
The Message Hash MUST be a hash of the Message Bundle. It MUST be hashed using SHA-256.
5. Address Format
Every Openmsg addesss MUST be formatted in the following way:
Format: <User ID> * <domain> ; a User ID and a domain separated by an asterix.
Example: 0123456*openmsg.me
The full address is a string with a maximum length of 255 characters. There is no set limit on the length of the User ID or domain, however the complete openmsg address, including the User ID, the asterix and the domain MUST NOT exceed the overall limit.

User ID MUST be a string, but MUST ONLY contain digits (0-9). As the User ID MUST be stored as a string, the User ID MAY start with leading zeros, for example 007*domain.com - therefore 007*domain.com and 7*domain.com are completely separate Addresses.

  • The domain identifies the user's node
  • The user ID must be unique per node
  • Maximum length of 255 characters
6. Protocol Overview
This is a very high level overview of the protocol. Not all steps are detailed here; some steps have been simplified or left out. See "Protocol in Detail" later in the document.
  • Users creates an account via a Provider and get a unique Address (e.g. 56789*domain.com)
  • To initiate communication, the sender must possess:
    • The recipient’s address
    • A valid one-time Pass Code issued by the recipient
  • The sender performs a handshake, the recipient confirms the Pass Code and generates and sends back:
    • An Ident Code
    • An Auth Code
    • A shared Encryption Key
  • Both Users store these three values
  • Messages can now be exchanged using the Ident Code, Auth Code and Encryption Key

The pass code is only needed once — during the initial handshake:
A handshake securely exchanges an identification code, auth codes and an encryption key.
After that, messages are encrypted, timestamped, and hashed using the shared auth code.

The recipient server:
Reconstructs the hash to confirm authenticity, freshness (within 60 seconds), and message integrity.
Verifies the sender’s domain by performing a callback to the domain in the senders address — ensuring the message was really sent from there.
7. Protocol in Detail
This part of the document details:
  • Creating Pass Codes
  • The Handshake
  • Sending and Receiving Messages
  • "Signup/Login with Openmsg"
7.1 Pass Codes
A Pass Code MUST be saved as a string so that each digit is preserved even if the leading digit is a zero. Each of the 6 digits in a Pass Code MUST be generated one digit at a time, each digit MUST be a true random number, and each digit MUST be appended to the Pass Code string one at a time. A Pass Code MUST be saved for later verification along with the timestamp of when it was created. It MUST be set to expire after one hour.
7.2 Data Transfer Format and Endpoints
All data transfers between Nodes — including during the Handshake and Message delivery — MUST be performed as HTTPS POST requests using JSON-encoded payloads.
Each Node MUST expose specific endpoints at fixed paths on its domain to receive this data. All Openmsg-related scripts MUST reside under the root path: /openmsg/
Subdirectories under /openmsg/ are used to separate different types of actions (e.g., /openmsg/auth/, /openmsg/message-receive/ etc). Each Node is responsible for hosting the appropriate handler scripts at these standardized locations to ensure interoperability.
Example: for the Openmsg address 012345*receivingdomain.com, the script responsible for receiving incoming messages MUST be located at:
https://receivingdomain.com/openmsg/message-receive/ as this is where sending Node will be making POSTS to.
This is the endpoint that sending nodes will use to perform POST requests when delivering messages.
7.3 The Handshake
Overview
To establish a connection between two Openmsg accounts, a handshake must be performed.
The node initiating the handshake is referred to as the Sending Node, and the node associated with the user being contacted is the Receiving Node.

To begin the handshake, the Sending Node must possess:
  • A valid Pass Code issued by the user on the Receiving Node.
  • The user's full Openmsg Address, from which the domain of the Receiving Node is derived.
The handshake is initiated by the Sending Node via an HTTPS POST request to the Receiving Node at:
https://<receiving-domain>/openmsg/auth/
Upon receiving the request, the Receiving Node:
  1. Validates the Pass Code for the user associated with the provided Openmsg address. This ensures that the recipient has explicitly granted permission for contact.
  2. Performs a callback by issuing an HTTPS POST request to the domain specified in the Sender's Openmsg address at:
    https://<receiving-domain>/openmsg/auth-confirm/
    This step verifies that the handshake was genuinely initiated by the correct Sending Node and prevents domain spoofing or unauthorized connection attempts.
The Sending Node confirms authenticity by responding with a JSON-encoded response.

Upon successful validation, the Receiving Node:
  1. Generates a unique Ident Code, Auth Code, and Message Encryption Key.
  2. Returns these values to the Sending Node via a JSON response.
Both Nodes then securely store the Ident Code, Auth Code, and Message Encryption Key associated with the respective pair of Users.
Result:
The handshake concludes with the creation and mutual exchange of:
  • An Ident Code for identifying the connection.
  • An Auth Code for authenticating future messages.
  • A Message Encryption Key for encrypting message content between the two parties.
Note: A new handshake can be performed to set up a new connection between two previously un-connected addresses. However, a handshake can also be performed by two currently connected addresses to replace or refresh codes and encryption keys.
7.3.1 Handshake Initiation (Step 1 – Sending Node → Receiving Node)
To establish a connection between two Openmsg users, the Sending Node MUST initiate a Handshake Request by sending an HTTPS POST request to the following endpoint on the Receiving Node:
https://<receiving-domain>/openmsg/auth/
7.3.2 Extracting the Receiving User ID
The receiving_openmsg_address is in the format:
<user_id>*<domain>
For example:
012345*receivingdomain.com
The receiving_openmsg_address_id is the portion before the * symbol — in this example: 012345. This is the unique numeric user ID on the Receiving Node’s domain.
7.3.3 Sending Node Responsibilities
Before initiating the handshake, the Sending Node MUST persist the following two pieces of data locally for validation in the upcoming callback step:
  • receiving_openmsg_address
  • pass_code
  • Current timestamp
These are used later to validate the callback from the Receiving Node confirming the legitimacy of the handshake. The timestamp will be used during this validation to make sure the handshake is fresh.
7.3.4 Request Payload
The Sending Node sends the following JSON-encoded data in the POST request body:
{
  "receiving_openmsg_address_id": "012345",
  "pass_code": "456123",
  "sending_openmsg_address": "987654*sendingdomain.com",
  "sending_openmsg_address_name": "Alice Smith",
  "sending_allow_replies": true,
  "openmsg_version": 1.0
}
Field Descriptions:
  • receiving_openmsg_address_id: The numeric user ID of the recipient on the Receiving Node. (Only the User ID part is sent not the whole address).
  • pass_code: A single-use, time-limited pass code previously issued by the recipient to allow this connection attempt.
  • sending_openmsg_address: Full Openmsg address of the sender, used to validate and complete the return handshake.
  • sending_openmsg_address_name: Optional display name associated with the sender's account, for UX purposes.
  • sending_allow_replies: Boolean flag indicating whether the sender permits responses ("false" used for one-way notification-type accounts).
  • openmsg_version: Version of the Openmsg protocol the client supports (used for version negotiation if needed).
7.4.1 Pass Code Verification & Callback (Step 2 – Receiving Node → Sending Node)
Once the Receiving Node has accepted a handshake request, it must verify that the handshake was genuinely initiated by the correct Sending Node. This is done via a callback request to the domain extracted from the sender’s Openmsg address.
7.4.2 Initial Pass Code Validation
Upon receiving the JSON POST request at:
https://<receiving-domain>/openmsg/auth/
The Receiving Node MUST:
  1. Extract the following fields from the request:
    • receiving_openmsg_address_id
    • pass_code
    • sending_openmsg_address
    • sending_openmsg_address_name
    • sending_allow_replies
    • openmsg_version
  2. Verify that the provided pass_code is:
    • Correct for the receiving user (as identified by receiving_openmsg_address_id)
    • Not expired (it MUST have been issued within the last 60 minutes)
If validation fails:
Return a JSON error response immediately:
{
  "error": true,
  "error_code": "pass_code_invalid",
  "error_message": "pass code not valid"
}
OR
{
  "error": true,
  "error_code": "pass_code_expired",
  "error_message": "expired pass code, over 1 hour old"
}
If validation succeeds:
  • The pass_code MUST be deleted or marked as used, to prevent reuse.
  • Proceed to the callback verification step.
7.4.3 Extracting Callback Details
Split the sending_openmsg_address into:
  • Sending User ID: the part before *
  • Sending Domain: the part after *
7.4.4 Perform Callback Verification
The Receiving Node MUST issue a POST request to the following endpoint on the Sending Node:
https://<receiving-domain>/openmsg/auth-confirm/
JSON Payload:
{
  "receiving_openmsg_address": "012345*receivingdomain.com",
  "pass_code": "123456"
}
Field Descriptions:
  • receiving_openmsg_address: The Openmsg address of the Receiving Node (i.e., the account being connected to).
  • pass_code: The same pass code originally submitted. This confirms to the Sending Node that the callback corresponds to the initial handshake request.
7.4.5 Purpose
This step ensures the following:
  • That the Receiving Domain issued the pass_code and that it is un-used and not expired.
  • That the original handshake request was actually initiated by the declared Sending Node (i.e., the domain in the address wasn't spoofed).
  • That the domain in the sender's address has a valid record of the pass_code, confirming intent to initiate the connection.
7.5.1 Handshake Verification (Step 3 - Sending Node)
This endpoint is used by the Receiving Node to confirm that the Sending Node originally initiated the handshake. It ensures that no third-party server is impersonating the Sending Node's domain.
7.5.2 Confirm Handshake
Upon receiving the JSON POST request at:
https://<sending-domain>/openmsg/auth-confirm/
The Receiving Node MUST:
  1. Extract the following fields from the request:
    • receiving_openmsg_address
    • pass_code
  2. Look up a previously stored handshake request that matches both:
    • receiving_openmsg_address
    • pass_code
  3. Validate timestamp:
    • Check that the stored handshake request was created within the last 60 seconds.
    • If the handshake has expired, return an error (see below).
  4. Delete or invalidate the pending handshake entry to prevent replay or reuse.
If validation fails:
Return a JSON error response immediately:
{
  "error": true,
  "error_code": "handshake_unknown",
  "error_message": "unknown pending authorization with openmsg address and pass_code"
}
OR
{
  "error": true,
  "error_code": "handshake_expired",
  "error_message": "expired handshake, over 60 seconds old"
}
If the handshake is valid and timely:
{
  "success": true
}
7.5.3 Purpose
This step ensures the following:
  • That original request did in fact originate from the domain in the sending_openmsg_address.
  • That the handshake request is fresh and hasnt expired.
7.6 Finalizing the Handshake (Returning Auth/Ident/Encryption Keys)
Once the Receiving Node has successfully validated the pass code and verified the handshake via the callback to the Sending Node, it proceeds to finalize the handshake.
7.6.1 – Callback Response Handling
Upon receiving the callback response (auth-confirm) from the Sending Node, the Receiving Node MUST:
  1. Parse the response and check for the presence of an error flag.
  2. If the response indicates an error, the handshake MUST be aborted and an error response returned to the original handshake initiator.
If the case of a failure:
If the callback response contains an error message, the Receiving Node MUST stop all further processing and return this error immediately to the Sending Node:
{
  "error": true,
  "error_code": "<error_code from callback>",
  "error_message": "Error: <message from callback>"
}
If there is another error, such as a 500 or 404, this SHOULD be returned to the Sending Node, for example:
{
  "error": true,
  "error_code": "http_error_status",
  "error_message": "Error: 500 error received from auth-confirm script"
}
If the case of a failure:
Proceed to next step
7.6.2 – Generating Shared Connection Keys
If the callback is successful (success: true), the Receiving Node MUST generate the following cryptographic materials:
  • auth_code: A 32-byte random string (hex encoded)
  • ident_code: A 32-byte random string (hex encoded)
  • message_crypt_key: A 32-byte symmetric key (hex encoded), generated using a secure key generator (e.g., sodium_crypto_secretbox_keygen()
These values form the shared connection keys between the two nodes.
Note: The two codes, auth_code and ident_code, MUST be cryptographically secure random values. All 3 values MUST be converted into hexadecimal (see: 4. Data Element Formats).
7.6.3 – Invalidating Previous Handshakes
Before storing the new connection, the Receiving Node MUST:
  • Delete any existing stored connection entries involving the same combination of the two addresses:
    • sending_openmsg_address
    • receiving_openmsg_address
This ensures the new values fully replace any codes or keys created in any previous handshake.
7.6.4 Storing New Connection
The Receiving Node MUST store the following values:
  • receiving_openmsg_address - Receiver’s Openmsg address
  • sending_openmsg_address - Sender’s Openmsg address
  • sending_openmsg_address_name - Human-readable display name of the sender
  • sending_allow_replies - Boolean indicating whether the sender accepts replies (defaults to true if not explicitly provided)
  • auth_code - Shared auth code for message validation
  • ident_code - Shared identifier used to recognize the connection
  • message_crypt_key - Symmetric encryption key used to encrypt/decrypt messages
Note: If sending_allow_replies is omitted or null, it MUST default to true.
--
--
--
Document in progress...