diff --git a/appengine/pom.xml b/appengine/pom.xml
index 4a7b7e11161..b3120f174cc 100644
--- a/appengine/pom.xml
+++ b/appengine/pom.xml
@@ -72,6 +72,7 @@
memcachemultitenancyoauth2
+ pusher-chatrequestssearchsendgrid
diff --git a/appengine/pusher-chat/README.md b/appengine/pusher-chat/README.md
new file mode 100644
index 00000000000..659018d7fb0
--- /dev/null
+++ b/appengine/pusher-chat/README.md
@@ -0,0 +1,47 @@
+# Pusher sample for Google App Engine
+
+This sample demonstrates how to use the [Pusher][pusher] on [Google App Engine][ae-docs].
+Pusher enables you to create public / private channels with presence information for real time messaging.
+This application demonstrates presence channels in Pusher using chat rooms.
+All users joining the chat room are authenticated using the `/authorize` endpoint.
+All users currently in the chat room receive updates of users joining / leaving the room.
+[Java HTTP library](https://github.com/pusher/pusher-http-java) is used for publishing messages to the channel
+and the [JS Websocket library](https://github.com/pusher/pusher-js) is used for subscribing.
+
+[pusher]: https://pusher.com
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+## Setup
+
+Install the [Google Cloud SDK](https://cloud.google.com/sdk/) and run:
+```
+ gcloud init
+```
+If this is your first time creating an App engine application:
+```
+ gcloud app create
+```
+
+#### Setup Pusher
+
+- Create a [Pusher] application and note down the `app_id`, `app_key`, `app_secret` and the cluster.
+- Update [appengine-web.xml](src/main/webapp/WEB-INF/appengine-web.xml) with these credentials.
+
+## Running locally
+
+```
+ mvn clean appengine:run
+```
+
+Access [http://localhost:8080](http://localhost:8080) via the browser, login and join the chat room.
+The chat window will contain a link you can use to join the room as a different user in another browser.
+You should now be able to view both the users within the chat application window and send messages to one another.
+
+## Deploying
+
+- Deploy the application to the project
+ ```
+ mvn clean appengine:deploy
+
+ ```
+ Access `https://YOUR_PROJECT_ID.appspot.com`
diff --git a/appengine/pusher-chat/pom.xml b/appengine/pusher-chat/pom.xml
new file mode 100644
index 00000000000..285c8625273
--- /dev/null
+++ b/appengine/pusher-chat/pom.xml
@@ -0,0 +1,75 @@
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine
+ appengine-pusher-chat
+
+
+ com.google.cloud
+ appengine-doc-samples
+ 1.0.0
+ ..
+
+
+
+ 1.7
+ 1.7
+
+
+
+
+ com.pusher
+ pusher-http-java
+ 1.0.0
+
+
+ com.google.guava
+ guava
+ 20.0
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.8.8
+
+
+ javax.servlet
+ servlet-api
+ 2.5
+ provided
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ 1.9.54
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 1.3.1
+
+
+
+
diff --git a/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/AuthorizeServlet.java b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/AuthorizeServlet.java
new file mode 100644
index 00000000000..e6e217de2f4
--- /dev/null
+++ b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/AuthorizeServlet.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.pusher;
+
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.common.io.CharStreams;
+import com.pusher.rest.Pusher;
+import com.pusher.rest.data.PresenceUser;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Authorization endpoint that is automatically triggered on `Pusher.subscribe` for private,
+ * presence channels. Successful authentication returns valid authorization token with user
+ * information.
+ *
+ * @see Pusher Authentication Docs
+ */
+// [START pusher_authorize]
+public class AuthorizeServlet extends HttpServlet {
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
+
+ // Instantiate a pusher connection
+ Pusher pusher = PusherService.getDefaultInstance();
+ // Get current logged in user credentials
+ User user = UserServiceFactory.getUserService().getCurrentUser();
+
+ // redirect to homepage if user is not authorized
+ if (user == null) {
+ response.sendRedirect("/");
+ return;
+ }
+ String currentUserId = user.getUserId();
+ String displayName = user.getNickname().replaceFirst("@.*", "");
+
+ String query = CharStreams.toString(request.getReader());
+ // socket_id, channel_name parameters are automatically set in the POST body of the request
+ // eg.socket_id=1232.12&channel_name=presence-my-channel
+ Map data = splitQuery(query);
+ String socketId = data.get("socket_id");
+ String channelId = data.get("channel_name");
+
+ // Presence channels (presence-*) require user identification for authentication
+ Map userInfo = new HashMap<>();
+ userInfo.put("displayName", displayName);
+
+ // Inject custom authentication code for your application here to allow /deny current request
+
+ String auth =
+ pusher.authenticate(socketId, channelId, new PresenceUser(currentUserId, userInfo));
+ // if successful, returns authorization in the format
+ // {
+ // "auth":"49e26cb8e9dde3dfc009:a8cf1d3deefbb1bdc6a9d1547640d49d94b4b512320e2597c257a740edd1788f",
+ // "channel_data":"{\"user_id\":\"23423435252\",\"user_info\":{\"displayName\":\"John Doe\"}}"
+ // }
+
+ response.getWriter().append(auth);
+ }
+
+ private static Map splitQuery(String query) throws UnsupportedEncodingException {
+ Map query_pairs = new HashMap<>();
+ String[] pairs = query.split("&");
+ for (String pair : pairs) {
+ int idx = pair.indexOf("=");
+ query_pairs.put(
+ URLDecoder.decode(pair.substring(0, idx), "UTF-8"),
+ URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
+ }
+ return query_pairs;
+ }
+}
+// [END pusher_authorize]
diff --git a/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/ChatServlet.java b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/ChatServlet.java
new file mode 100644
index 00000000000..1333bee3f4a
--- /dev/null
+++ b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/ChatServlet.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.pusher;
+
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** Homepage of chat application, redirects user to login page if not authorized. */
+public class ChatServlet extends HttpServlet {
+
+ public static String getUriWithChatRoom(HttpServletRequest request, String chatRoom) {
+ try {
+ String query = "";
+ if (chatRoom != null) {
+ query = "room=" + chatRoom;
+ }
+ URI thisUri = new URI(request.getRequestURL().toString());
+ URI uriWithOptionalRoomParam =
+ new URI(
+ thisUri.getScheme(),
+ thisUri.getUserInfo(),
+ thisUri.getHost(),
+ thisUri.getPort(),
+ "/",
+ query,
+ "");
+ return uriWithOptionalRoomParam.toString();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ final UserService userService = UserServiceFactory.getUserService();
+ User currentUser = userService.getCurrentUser();
+ String room = req.getParameter("room");
+ // Show login link if user is not logged in.
+ if (currentUser == null) {
+ String loginUrl = userService.createLoginURL(getUriWithChatRoom(req, room));
+ resp.getWriter().println("
");
+ return;
+ }
+
+ // user is already logged in
+ if (room != null) {
+ req.setAttribute("room", room);
+ }
+ getServletContext().getRequestDispatcher("/WEB-INF/view/chat.jsp").forward(req, resp);
+ }
+}
diff --git a/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/PusherService.java b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/PusherService.java
new file mode 100644
index 00000000000..f733fdf7218
--- /dev/null
+++ b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/PusherService.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.pusher;
+
+import com.pusher.rest.Pusher;
+
+// [START pusher_server_initialize]
+public abstract class PusherService {
+
+ public static final String APP_KEY = System.getenv("PUSHER_APP_KEY");
+ public static final String CLUSTER = System.getenv("PUSHER_CLUSTER");
+
+ private static final String APP_ID = System.getenv("PUSHER_APP_ID");
+ private static final String APP_SECRET = System.getenv("PUSHER_APP_SECRET");
+
+ private static Pusher instance;
+
+ static Pusher getDefaultInstance() {
+ if (instance != null) {
+ return instance;
+ } // Instantiate a pusher
+ Pusher pusher = new Pusher(APP_ID, APP_KEY, APP_SECRET);
+ pusher.setCluster(CLUSTER); // required, if not default mt1 (us-east-1)
+ pusher.setEncrypted(true); // optional, ensure subscriber also matches these settings
+ instance = pusher;
+ return pusher;
+ }
+}
+// [END pusher_server_initialize]
diff --git a/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/SendMessageServlet.java b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/SendMessageServlet.java
new file mode 100644
index 00000000000..4119439bdbc
--- /dev/null
+++ b/appengine/pusher-chat/src/main/java/com/example/appengine/pusher/SendMessageServlet.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.pusher;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.common.io.CharStreams;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.pusher.rest.data.Result;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Submit a chat message over a channel. Note : we use socket_id to exclude the sender from
+ * receiving the message // {@see
+ * Excluding
+ * Recipients}
+ */
+// [START pusher_server_send_message]
+public class SendMessageServlet extends HttpServlet {
+
+ private Gson gson = new GsonBuilder().create();
+ private TypeReference