diff --git a/lib/types/tunnelbroker/messages.js b/lib/types/tunnelbroker/messages.js
--- a/lib/types/tunnelbroker/messages.js
+++ b/lib/types/tunnelbroker/messages.js
@@ -23,6 +23,7 @@
   type MessageToTunnelbrokerRequest,
   messageToTunnelbrokerRequestValidator,
 } from './message-to-tunnelbroker-request-types.js';
+import { type TunnelbrokerAPNsNotif } from './notif-types.js';
 import {
   type ConnectionInitializationMessage,
   connectionInitializationMessageValidator,
@@ -51,6 +52,7 @@
   CONNECTION_INITIALIZATION_MESSAGE: 'ConnectionInitializationMessage',
   CONNECTION_INITIALIZATION_RESPONSE: 'ConnectionInitializationResponse',
   ANONYMOUS_INITIALIZATION_MESSAGE: 'AnonymousInitializationMessage',
+  TUNNELBROKER_APNS_NOTIF: 'APNsNotif',
   MESSAGE_TO_DEVICE_REQUEST_STATUS: 'MessageToDeviceRequestStatus',
   MESSAGE_TO_DEVICE_REQUEST: 'MessageToDeviceRequest',
   MESSAGE_TO_TUNNELBROKER_REQUEST: 'MessageToTunnelbrokerRequest',
@@ -79,4 +81,5 @@
   | MessageToDevice
   | MessageReceiveConfirmation
   | Heartbeat
-  | MessageToTunnelbrokerRequest;
+  | MessageToTunnelbrokerRequest
+  | TunnelbrokerAPNsNotif;
diff --git a/lib/types/tunnelbroker/notif-types.js b/lib/types/tunnelbroker/notif-types.js
new file mode 100644
--- /dev/null
+++ b/lib/types/tunnelbroker/notif-types.js
@@ -0,0 +1,9 @@
+// @flow
+
+export type TunnelbrokerAPNsNotif = {
+  +type: 'APNsNotif',
+  +headers: string,
+  +clientMessageID: string,
+  +deviceID: string,
+  +payload: string,
+};
diff --git a/shared/tunnelbroker_messages/src/messages/mod.rs b/shared/tunnelbroker_messages/src/messages/mod.rs
--- a/shared/tunnelbroker_messages/src/messages/mod.rs
+++ b/shared/tunnelbroker_messages/src/messages/mod.rs
@@ -8,6 +8,7 @@
 pub mod message_to_device_request_status;
 pub mod message_to_tunnelbroker;
 pub mod message_to_tunnelbroker_request;
+pub mod notif;
 pub mod session;
 
 pub use device_list_updated::*;
@@ -23,6 +24,7 @@
   ConnectionInitializationResponse, ConnectionInitializationStatus, Heartbeat,
 };
 
+use crate::notif::*;
 use serde::{Deserialize, Serialize};
 
 // This file defines types and validation for messages exchanged
@@ -42,6 +44,7 @@
   // MessageToDeviceRequestStatus must be placed before MessageToDeviceRequest.
   // This is due to serde's pattern matching behavior where it prioritizes
   // the first matching pattern it encounters.
+  APNsNotif(APNsNotif),
   MessageToDeviceRequestStatus(MessageToDeviceRequestStatus),
   MessageToDeviceRequest(MessageToDeviceRequest),
   MessageToDevice(MessageToDevice),
diff --git a/shared/tunnelbroker_messages/src/messages/notif.rs b/shared/tunnelbroker_messages/src/messages/notif.rs
new file mode 100644
--- /dev/null
+++ b/shared/tunnelbroker_messages/src/messages/notif.rs
@@ -0,0 +1,14 @@
+//! APNs notif built on client.
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(tag = "type", rename_all = "camelCase")]
+pub struct APNsNotif {
+  pub headers: String,
+  #[serde(rename = "clientMessageID")]
+  pub client_message_id: String,
+  #[serde(rename = "deviceID")]
+  pub device_id: String,
+  pub payload: String,
+}