diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index d6332e5..71c40cc 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1467,6 +1467,54 @@ The commands accepted in walsender mode are:
+ Primary keepalive message (B)
+
+
+
+
+
+
+ Byte1('k')
+
+
+
+ Identifies the message as a sender keepalive.
+
+
+
+
+
+ Byte8
+
+
+
+ The current end of WAL on the server, given in
+ XLogRecPtr format.
+
+
+
+
+
+ Byte8
+
+
+
+ The server's system clock at the time of transmission,
+ given in TimestampTz format.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Standby status update (F)
diff --git a/src/include/replication/walprotocol.h b/src/include/replication/walprotocol.h
index 656c8fc..1c73d35 100644
--- a/src/include/replication/walprotocol.h
+++ b/src/include/replication/walprotocol.h
@@ -40,6 +40,21 @@ typedef struct
} WalDataMessageHeader;
/*
+ * Keepalive message from primary (message type 'k'). (lowercase k)
+ * This is wrapped within a CopyData message at the FE/BE protocol level.
+ *
+ * Note that the data length is not specified here.
+ */
+typedef struct
+{
+ /* Current end of WAL on the sender */
+ XLogRecPtr walEnd;
+
+ /* Sender's system clock at the time of transmission */
+ TimestampTz sendTime;
+} PrimaryKeepaliveMessage;
+
+/*
* Reply message from standby (message type 'r'). This is wrapped within
* a CopyData message at the FE/BE protocol level.
*