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's server.
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 persisted 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 Nonce
The message encryption nonce MUST be created using Sodium's SECRETBOX RANDOMBYTES function. It will be used in its binary format during message encryption.
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 persist 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.
The next parts of the document details:
  • Creating Pass Codes
  • The Handshake
  • Sending and Receiving Messages
  • "Signup/Login with Openmsg"
7. The Handshake
7.1 Pass Codes
A Pass Code MUST be stored 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 persisted 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 Protocol
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:
❯❯ URL

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:
    ❯❯ URL

    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 persist 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:
❯❯ URL

https://<receiving-domain>/openmsg/auth/
The receiving domain is extracted from the Receiver's Openmsg Address as shown below.
7.3.2 Extracting the Receiving User ID and Domain
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.
The receiver's domain is the portion after the * symbol.
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:
❯❯ JSON
{
  "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 the handshake.
7.4.2 Initial Pass Code Validation
Upon receiving the JSON POST request at:
❯❯ URL

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:
❯❯ JSON
{
  "error": true,
  "error_code": "pass_code_invalid",
  "error_message": "pass code not valid"
}
OR
❯❯ JSON
{
  "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
To prevent a Node from completing a handshake using an Openmsg address from a different domain (domain spoofing), (even if they have a valid pass_code), the Receiving Node performs a domain ownership verification. The Receiving Node MUST 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:
❯❯ URL

https://<receiving-domain>/openmsg/auth-confirm/
JSON Payload:
❯❯ JSON
{
  "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:
❯❯ URL

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:
❯❯ JSON
{
  "error": true,
  "error_code": "handshake_unknown",
  "error_message": "unknown pending authorization with openmsg address and pass_code"
}
OR
❯❯ JSON
{
  "error": true,
  "error_code": "handshake_expired",
  "error_message": "expired handshake, over 60 seconds old"
}
If the handshake is valid and timely:
❯❯ JSON
{
  "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.
In 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:
❯❯ JSON
{
  "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:
❯❯ JSON
{
  "error": true,
  "error_code": "http_error_status",
  "error_message": "Error: 500 error received from auth-confirm script"
}
In the case of success:
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 persist 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.
8. Sending a Message
8.1 Node Roles
Once a successful handshake has been performed between two Nodes, both Nodes will have the same auth_code, ident_code, and message_crypt_key stored for the respective Openmsg addresses. Either Node can now act as a Sending Node or a Receiving Node. These roles are dynamic and depend only on the direction of the message being sent.
8.2 The Message Protocol
Overview
Before a message can be sent between two users, their respective Nodes MUST have completed a successful handshake.

In order to send a message, the Sending Node MUST have the full openmsg_address of the recipient Both Nodes MUST have the same shared auth_code, ident_code, and message_crypt_key associated with that recipient, obtained during the handshake


To send a message securely and verifiably, the Sending Node MUST construct a secure payload and MUST prepare for a verification callback. The Receiving Node performs several validation steps, including a callback to the senders domain before decrypting and storing the message.

The message sending process in Openmsg consists of two components:
  • Sending Component – the logic on the Sending Node responsible for composing, encrypting, and POSTing the message to the appropriate endpoint on the recipient’s Node.
  • Receiving Component – the logic on the Receiving Node responsible for validating, decrypting, and storing or displaying the incoming message.
The Sending Node MUST:
  • encrypt the message using the message_crypt_key and a message_nonce
  • create a hash (message_hash) of the encrypted message & nonce (message_package) using a salt (message_salt), the auth_code and the current unix timestamp (message_timestamp)
  • store the message_hash, message_nonce and the message_timestamp for later validation during a callback
  • send specific data to the receiving node
The Sending Node sends the data via an HTTPS POST request to the Receiving Node at:
❯❯ URL

https://<receiving-domain>/openmsg/message-receive/
The Receiving Node MUST be listening for incoming POSTs at the above endpoint, where it will:
  1. Locate the connected user details using the ident_code
  2. Verify the authenticity and freshness of the message data by recreating the hash using the auth_code it already has
  3. Performs a callback by issuing an HTTPS POST request to the domain specified in the Sender's Openmsg address at:
  4. ❯❯ URL

    https://<sending-domain>/openmsg/message-confirm/
    This step verifies that the message was genuinely initiated by the correct Sending Node and prevents domain spoofing or unauthorized message attempts.
The Sending Node confirms it initiated that message by responding with a JSON-encoded response.

Upon successful validation, the Receiving Node should:
  1. Decrypt the message using the message_crypt_key
  2. Store or process the message for the recipient user
Response / Error Codes:
When sending or receiving a message, Nodes MUST responde with set error codes (error_code) and response codes (response_code) so that errors can be handled effeciently. The plain text error messgage (error_message) can be customized.
❯❯ Plain Text
Errors:
error = TRUE
response_code | error_code | Plain Text

SM_E000 | unknown_error | message_data_missing | unknown error // Other error
SM_E001 | ident_code_invalid | user not known
SM_E002 | message_rejected_byuser | This address does not accept incomming messages // (Can be used for noreply addresses or as a quiet way for a User to block a sender)
SM_E003 | message_unknown | Unknown Message. No outgoing message matching these details or message was sent from an unauthorized domain domain.
SM_E004 | message_hash_invalid | could not recreate hash - auth_code mismatch, assuming message_timestamp and message_package were sent correctly
SM_E005 | message_expired | Message sent over 60 seconds ago. Expired message request. 
SM_E006 | message_decryption_error | The message decryption failed. Encryption key mis-match or corrupt message, assuming nonce was sent correctly
SM_E007 | message_nonce_error | This message encryption nonce has been used before for this connection
SM_E008 | message_rejected_temp | Message rejected temporary - server is being maintained
SM_E009 | message_rejected_perm | Message rejected permanently (service has closed down, messages from sending domain or IP address are blocked)


Success:
success = TRUE
response_code | Plain Text

SM_S888 | Message received successfully and accepted
                        
8.2.1 Constructing and Sending the Message (Step 1 – Sending Node → Receiving Node)
To initiate sending a message, the Sending Node MUST perform the following steps:
8.2.2 Encrypt the message
Nonce
The Sending Node MUST generate a cryptographically secure message nonce using libsodium's SECTRETBOX NONCEBYTES. A new nonce MUST be freshly created for each message. The binary nonce will be used for encryption (message_nonce). This nonce MUST also be Base64 encoded using Sodium's binary to base64 function with the URL SAFE NO PADDING flag MUST be set (message_nonce_encoded). The Base64 version MUST be persisted ready for a verification callback from the Receiving Node.

Message Encryption
The Sending Node MUST encrypt the plaintext message using the shared message_crypt_key and the new message_nonce using libsodium's SECRETBOX: sodium_crypto_secretbox (message_encrypted). A message_package MUST then be created ready to send to the Receiving Node.

Message Package
The Message Package (message_package) MUST be a string concatenation of:
❯❯ Pseudo Code

message_nonce + message_encrypted
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.

8.2.3 Generate a Message Hash
A Message Hash (message_hash) MUST now be created by the Sending Node. The Message Hash is used by the Receiving Node verify that the Sending Node has the correct auth_code, that the timestamp is genuine and the message is fresh, and that the message or any of the data hasn't been altered. The Message Hash is also used in a callback request to verify the Sending Node.

Message Hash Salt
The Sending Node MUST enerate a random salt (message_salt). The 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 before use. The output MUST be an ASCII string containing the hexadecimal representation of the random bytes. A Salt MUST NOT be reused.

Message Timestamp
Record the current Unix timestamp (message_timestamp) as a string.

Compute the message_hash
Generate the message_hash. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
❯❯ Pseudo Code

message_package + auth_code + message_salt + message_timestamp
in that order, with no extra characters or spaces added.

Persist the outgoing message metadata ("Outgoing Message")
The Sending Node MUST persist the following fields temporarily:
  • message_hash
  • message_nonce_encoded
  • message_timestamp
This data will be used in a call back from the Receiving Node. The Sending Node will use this data to confirm that it did send the message.

Optional but best practice: The Sending Node MAY also store the following fields temporarily:
  • sender's openmsg address
  • ident_code
  • message_text
Storing the above 3 data points will mean the sending User can view the message text in an "outbox" in case of an error, meaning the message text will not be lost to the User.
8.2.4 Send the Message to the Receiving Node via HTTPS POST:
The Sending Node MUST issue a POST request to the following endpoint on the Receiving Node:
❯❯ URL

https://<receiving-domain>/openmsg/message-receive/
The receiving domain is extracted from the Receiver's Openmsg Address. See 7.3.2.

JSON Payload:
❯❯ JSON
{
    "receiving_openmsg_address_id": "678901",
    "sending_openmsg_address_name": "New Display Name",
    "ident_code": "<shared-ident-code>",
    "message_package": "<base64-encoded nonce and encrypted message>",
    "message_hash": "<hash value>",
    "message_salt": "<randomly-generated hexadecimal salt>",
    "message_timestamp": 1721225033,
    "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).
  • sending_openmsg_address_name: Human-readable display name of the sender. Only include this if the Sending User wishes to update the Receiving User with a new display name.
  • ident_code: The ident_code created during the Handshake
  • openmsg_version: The current version of the Openmsg protocol being used by the Sending Node
8.3.1 Validating the Message & Callback (Step 2 – Receiving Node → Sending Node)
Once the Receiving Node receives an incoming message POST, it must verify the message

Upon receiving the JSON POST request at:
❯❯ URL

https://<receiving-domain>/openmsg/message-receive/
The Receiving Node MUST:
  1. Extract the following fields from the request:
    • receiving_openmsg_address_id *
    • sending_openmsg_address_name
    • ident_code *
    • message_package *
    • message_hash *
    • message_salt *
    • message_timestamp *
    • openmsg_version
The fields marked with an asterix are required.
8.3.2 Initial Message Validation
The Receiving Node MUST now check that all the required data is present in the JSON POST. See 8.2.5 for requiried fields. If any of the required fields are missing the Node MUST return a JSON error response immediately:
❯❯ JSON
{
  "error": true,
  "response_code", "SM_E000"
  "error_code": "message_data_missing",
  "error_message": "Missing data from message JSON POST"
}
If all the fields are present, the Receiving Node MUST now:
  1. Look up the connection record matching the User receiving_openmsg_address_id and the connection identification code ident_code
  2. Retrieve the corresponding auth_code
  3. Recompute the hash using the exact same method as explained in 8.3.1. The hash MUST be created from a concatenated string of:
    ❯❯ Pseudo Code

    message_package + auth_code + message_salt + message_timestamp
    Where the data for message_package, message_salt, message_timestamp MUST be taken from the POST originating from the Sending Node. The auth_code MUST be the Auth Code that was retreived by the Receiving Node in points 1 & 2 in this list of actions.
  4. The resulting hash MUST be compared with the message_hash received from the Sending Node
  5. Verifie that message_timestamp is within an acceptable freshness window (e.g., no older than 60 seconds)
If validation fails:
If there is no matching connection with the fields receiving_openmsg_address_id & ident_code, if the hash does not match or the timestamp is expired, the message MUST be rejected. Return a JSON error response immediately:
❯❯ JSON
{
  "error": true,
  "response_code", "SM_E001"
  "error_code": "ident_code_invalid",
  "error_message": "No matching connection between these two users. ident_code not valid."
}
OR
❯❯ JSON
{
  "error": true,
  "response_code", "SM_E004"
  "error_code": "message_hash_invalid",
  "error_message": "There was an error with the message hash or authorization."
}
OR
❯❯ JSON
{
  "error": true,
  "response_code", "SM_E005"
  "error_code": "message_expired",
  "error_message": "Message / message hash is too old."
}
8.3.3 Extracting Callback Details
To prevent unauthorized domains from sending forged messages (even if they have the correct keys), the Receiving Node performs a domain verification callback. The Receiving Node MUST:
  1. Look up the sender's Openmsg address (sending_openmsg_address) associated with the connection (using the receiver's openmsg address and (ident_code).
  2. Extract the domain from the (sending_openmsg_address):
    • Sending User ID: the part before *
    • Sending Domain: the part after *
8.3.4 Domain Verification via Callback
The Receiving Node MUST then issue a POST request to the following endpoint on the Sending Node:
❯❯ URL

https://<sending-domain>/openmsg/message-confirm/
JSON Payload:
❯❯ JSON
{
    "message_nonce": "<Base64 encoded nonce value>",
    "message_hash": "<hash value>"
}
The message nonce sent for verification in the JSON payload MUST be the Base64 encoded version. See 8.2.2
8.3.5 Purpose
This step ensures the following:
  • That the sender of the message knew the shared secret auth_code
  • That the message is fresh and that no details have been altered
  • That the original message request was actually initiated by the declared Sending Node (i.e., the domain in the address wasn't spoofed).
8.4.1 Message Verification (Step 3 - Sending Node)
This endpoint is used by the Receiving Node to confirm that the correct Sending Node originally initiated the message. It ensures that no third-party server is impersonating the Sending Node's domain.
8.4.2 Confirm the Message
Upon receiving the JSON POST request at:
❯❯ URL

https://<sending-domain>/openmsg/message-confirm/
The Receiving Node MUST:
  1. Extract the following fields from the request:
    • message_nonce
    • message_hash
  2. Look up a previously stored outgoing message that matches both:
    • message_nonce
    • message_hash
  3. Validate timestamp:
    • Check that the stored outgoing message was initiated within the last 60 seconds.
    • If the outgoing message has expired, return an error (see below).
If validation fails:
Return a JSON error response immediately:
❯❯ JSON
{
  "error": true,
  "response_code": "SM_E000",
  "error_code": "missing_data",
  "error_message": "Missing message_hash or message_nonce "
}
OR
❯❯ JSON
{
  "error": true,
  "response_code": "SM_E003",
  "error_code": "message_unknown",
  "error_message": "Unknown Message. No outgoing message matching these details or message was sent from an unauthorized domain domain."
}
OR
❯❯ JSON
{
  "error": true,
  "response_code": "SM_E005",
  "error_code": "message_expired",
  "error_message": "Message sent over 60 seconds ago. Expired message request."
}
If the message is valid and timely:
❯❯ JSON
{
  "success": true
}
8.4.3 Purpose
This step ensures the following:
    That original request did in fact originate from the domain in the senders Openmsg address. That the message is fresh and hasnt expired.
8.5.1 Final Verification and Message Storage (Step 4 - Receiving Node)
Once the Receiving Node has successfully validated the message hash and verified the Sending Node via the callback to the senders domain, it proceeds to finalize the message.
8.5.2 Check the Nonce is Unique
Upon receiving a successful callback response from the Sending Node, the Receiving Node SHOULD check that the Sending Node has not used the same Nonse before for that Sender/User connection. If the same Nonce has been used before for that connection then the message SHOULD be rejected:
❯❯ JSON
{
  "error": true,
  "response_code": "SM_E007",
  "error_code": "message_nonce_error",
  "error_message": "This message encryption nonce has been used before for this sender/receiver connection."
}
8.5.3 Decrypt the Message
The Receiving Node MUST now attempt to decrypt the message (message_encrypted) using the message_crypt_key and message_nonce. The Receiver MUST use libsodium's "secretbox open" function. The Receiving Node MUST use the binary encoded version of message_crypt_key. As message_crypt_key will have been stored as hexdecimal encoding, it MUST be encoded into binary using libsodium's hexdecimal to binary function. The result will be the decrypted plaintext message (message_decrypted).
❯❯ Pseudo Code
message_decrypted = sodium_crypto_secretbox_open(message_encrypted, message_nonce, sodium_hex2bin(message_crypt_key))
In the case of a failure:
If the decryption fails, such as due to the message_crypt_key not being correct, the message MUST be rejected immediately:
❯❯ JSON
{
  "error": true,
  "response_code": "SM_E006",
  "error_code": "message_decryption_error",
  "error_message": "The message decryption failed. Encryption key mis-match or corrupt message."
}
8.5.4 Store the Message
Once the message has been decrypted successfully, it needs saving for the end User to access. It would be up to each Node to decide what format or encoding to store the message in. The Receiving Node can choose to encode the message or re-encrypt the message before saving.

WARNING

The encrypted version of the message (message_encrypted) that was received from the Sending Node should not be stored, or should not be stored as the main or only copy of the message; If the users refresh their encryption key, all stored messages would be unreadable and without the possibility of being decrypted (unless previous encryption keys are persisted for each message). The Node may instead choose to have their own private encryption key for that User's messages (no matter who the sender is), or one that is shared for all Users for all messages stored on that Node. The decrypted messages can then be re-encrypted using the Node's own encryption key.
The Receiving Node MUST persist the message alongside the Receiver's Openmsg Address, the ident_code AND the message_hash.
Note: The Message Hash (message_hash) will be used for identifying the message between Nodes for future operations such as Read Receipts, therefore storing message_hash is imperative.
The Receiving Node MUST persist the Nonce (message_nonce) against the ident_code to prevent future reuse (nonce replay protection).

In the case of success:
The following JSON POST MUST be returned upon successful receipt of a message:
❯❯ JSON
{
  "success": true,
  "response_code": "SM_S888"
}
8.6 Mark as Sent (Step 5 - Sending Node)
Once the Receiving Node has successfully received the message and sent a SUCCESS response to the Sending Node the Sending Node MUST mark the message as "Sent":

The Sending Node MUST delete the Outgoing Message (persisted in 8.2.3)

The Sending Node SHOULD persist the message_hash against sending_openmsg_address and ident_code. The Receiving Node might make a JSON POST to the Sending Node in the Future to mark the message as "Read" when the Receiving User reads the message.
.
.
.
.