diff --git a/cmdline/EosCtaStub.cpp b/cmdline/EosCtaStub.cpp
index 7badf903ebfb684c0800ad006533732bdc98bdd2..6775f2b2aac3e842ceec90ccb92a92b3b6767f45 100644
--- a/cmdline/EosCtaStub.cpp
+++ b/cmdline/EosCtaStub.cpp
@@ -69,6 +69,21 @@ void RequestCallback<eos::wfe::Alert>::operator()(const eos::wfe::Alert &alert)
    OutputJsonString(&alert);
 }
 
+
+
+/*!
+ * Convert exceptions into Alerts.
+ *
+ * This tells the framework how to log exceptions received on the client side
+ */
+
+template<>
+void ExceptionToAlert<eos::wfe::Alert>::operator()(const std::exception &e, eos::wfe::Alert &alert)
+{
+   alert.set_audience(eos::wfe::Alert::EOSLOG);
+   alert.set_message_txt(e.what());
+}
+
 } // namespace XrdSsiPb
 
 
@@ -299,7 +314,7 @@ int exceptionThrowingMain(int argc, const char *const *const argv)
 
    std::cout << "Request sent, going to sleep..." << std::endl;
 
-   int wait_secs = 5;
+   int wait_secs = 20;
 
    while(wait_secs--)
    {
diff --git a/eos/messages/eos_messages.proto b/eos/messages/eos_messages.proto
index fcdc333f70d181f95bb7d881be52bd2b618b0265..3fb6c963f8975b8e746c8106abecbcd54f28bd36 100644
--- a/eos/messages/eos_messages.proto
+++ b/eos/messages/eos_messages.proto
@@ -40,7 +40,7 @@ message Security {
 message Alert {
   enum Audience { EOSLOG = 0; ENDUSER = 1; }
   Audience audience          = 1;       //< The intended audience of the error message
-  string message             = 2;       //< An empty if success, else an error message
+  string message_txt         = 2;       //< An empty if success, else an error message
 }
 
 
diff --git a/xroot_plugins/XrdSsiCtaRequestProc.cpp b/xroot_plugins/XrdSsiCtaRequestProc.cpp
index 6056e79db99ba8c32c1e94fa8e45147d8c03fa02..266ec775e60e4f142a218583b01244917cdb39ec 100644
--- a/xroot_plugins/XrdSsiCtaRequestProc.cpp
+++ b/xroot_plugins/XrdSsiCtaRequestProc.cpp
@@ -154,16 +154,14 @@ void RequestProc<eos::wfe::Notification, eos::wfe::Response, eos::wfe::Alert>::E
 template <>
 void RequestProc<eos::wfe::Notification, eos::wfe::Response, eos::wfe::Alert>::ExecuteAlerts()
 {
-   // Allocate alert messages on the heap, they are self-destructing
+   eos::wfe::Alert alert_msg;
 
-   auto *alert_msg = new eos::wfe::Alert();
+   alert_msg.set_audience(eos::wfe::Alert::EOSLOG);
+   alert_msg.set_message_txt("Something bad happened");
 
-   alert_msg->set_audience(eos::wfe::Alert::EOSLOG);
-   alert_msg->set_message("Something bad happened");
+   // Serialize and send the alert message
 
-   // Send the alert message
-
-   Alert(*alert_msg);
+   Alert(alert_msg);
 }
 
 
@@ -187,7 +185,7 @@ void RequestProc<eos::wfe::Notification, eos::wfe::Response, eos::wfe::Alert>::
       //case PB_PARSE_ERR:    m_metadata.mutable_exception()->set_code(eos::wfe::Exception::PB_PARSE_ERR);
    //}
 
-   m_metadata.mutable_alert_msg()->set_message(err_text);
+   m_metadata.mutable_alert_msg()->set_message_txt(err_text);
 
    // Output message in Json format (for debugging)
 
diff --git a/xroot_ssi_pb/XrdSsiPbRequest.h b/xroot_ssi_pb/XrdSsiPbRequest.h
index 5e790d4bbeaeadda0ec76e59355a9fe940a00c7d..f1b2e85182be1ac125e7e4dbf0aee22735e13b51 100644
--- a/xroot_ssi_pb/XrdSsiPbRequest.h
+++ b/xroot_ssi_pb/XrdSsiPbRequest.h
@@ -26,7 +26,7 @@ namespace XrdSsiPb {
 /*!
  * XRootD SSI + Protocol Buffers Callback class
  *
- * The client should specialize on this class for each XRootD reply type (Metadata, Alert, Error)
+ * The client should specialize on this class for each XRootD reply type (Metadata, Alert)
  */
 
 template<typename CallbackArg>
@@ -38,6 +38,21 @@ public:
 
 
 
+/*!
+ * Convert Exceptions to Alerts
+ *
+ * The client should specialize on this class to specify how to log exceptions
+ */
+
+template<typename AlertType>
+class ExceptionToAlert
+{
+public:
+   void operator()(const std::exception &e, AlertType &alert);
+};
+
+
+
 /*!
  * XRootD SSI + Protocol Buffers Request class
  */
@@ -100,16 +115,21 @@ public:
    virtual XrdSsiRequest::PRD_Xeq ProcessResponseData(const XrdSsiErrInfo &eInfo, char *buff, int blen, bool last) override;
 
    /*!
-    * The Alert method is optional. By default, Alert messages are ignored.
+    * Deserialize Alert messages and call the Alert callback
     */
 
-   virtual void Alert(XrdSsiRespInfoMsg &aMsg) override;
+   virtual void Alert(XrdSsiRespInfoMsg &alert_msg) override;
+
 
 private:
    const char *m_request_bufptr;          //!< Pointer to the Request buffer
    int         m_request_len;             //!< Size of the Request buffer
    int         m_response_bufsize;        //!< Size of the Response buffer
 
+   // Convert exceptions to Alerts. Must be defined on the client side.
+
+   ExceptionToAlert<AlertType> ExceptionHandler;
+
    // Callbacks for each of the XRootD reply types
 
    RequestCallback<MetadataType> MetadataCallback;
@@ -136,40 +156,26 @@ bool Request<RequestType, MetadataType, AlertType>::ProcessResponse(const XrdSsi
    std::cout << "[DEBUG] ProcessResponse(): response type = " << rInfo.State() << std::endl;
 #endif
 
-   switch(rInfo.rType)
-   {
+   try {
+      switch(rInfo.rType) {
+
       // Handle errors in the XRootD framework (e.g. no response from server)
 
-      case XrdSsiRespInfo::isError:
-         //ErrorCallback(eInfo.Get());
-         Finished();    // Return control of the object to the calling thread and delete rInfo
-
-         // Andy says it is now safe to delete the Request object, which implies that the pointer on the calling side
-         // will never refer to it again and the destructor of the base class doesn't access any class members.
-
-         delete this;
-         break;
-
-      case XrdSsiRespInfo::isHandle:
-         // To implement detached requests, add another callback type which saves the handle
-         //ErrorCallback("Detached requests are not implemented.");
-         Finished();
-         delete this;
-         break;
-
-      case XrdSsiRespInfo::isFile:
-         // To implement file requests, add another callback type
-         //ErrorCallback("File requests are not implemented.");
-         Finished();
-         delete this;
-         break;
-
-      case XrdSsiRespInfo::isStream:
-         // To implement stream requests, add another callback type
-         //ErrorCallback("Stream requests are not implemented.");
-         Finished();
-         delete this;
-         break;
+      case XrdSsiRespInfo::isError:     throw XrdSsiException(eInfo.Get());
+
+      // To implement detached requests, add another callback type which saves the handle
+
+      case XrdSsiRespInfo::isHandle:    throw XrdSsiException("Detached requests are not implemented.");
+
+      // To implement file requests, add another callback type
+
+      case XrdSsiRespInfo::isFile:      throw XrdSsiException("File requests are not implemented.");
+
+      // To implement stream requests, add another callback type
+
+      case XrdSsiRespInfo::isStream:    throw XrdSsiException("Stream requests are not implemented.");
+
+      // Metadata-only responses and data responses
 
       case XrdSsiRespInfo::isNone:
       case XrdSsiRespInfo::isData:
@@ -191,17 +197,14 @@ bool Request<RequestType, MetadataType, AlertType>::ProcessResponse(const XrdSsi
             }
             else
             {
-               //ErrorCallback("metadata.ParseFromArray() failed");
-               Finished();
-               delete this;
-               break;
+               throw PbException("metadata.ParseFromArray() failed");
             }
          }
 
-         // If this is a metadata-only response, there is nothing more to do
-
          if(rInfo.rType == XrdSsiRespInfo::isNone)
          {
+            // If this is a metadata-only response, we are done
+
             Finished();
             delete this;
             break;
@@ -216,6 +219,24 @@ bool Request<RequestType, MetadataType, AlertType>::ProcessResponse(const XrdSsi
 
             GetResponseData(response_bufptr, m_response_bufsize);
          }
+      }
+   }
+   catch(std::exception &e)
+   {
+      // Pass the exception to the Alert callback to be logged
+
+      AlertType alert;
+      ExceptionHandler(e, alert);
+      AlertCallback(alert);
+
+      // Return control of the object to the calling thread and delete rInfo
+
+      Finished();
+
+      // It is now safe to delete the Request object (which implies that the pointer on the calling side
+      // will never refer to it again and the destructor of the base class doesn't access any class members).
+
+      delete this;
    }
 
    return true;
@@ -231,22 +252,7 @@ XrdSsiRequest::PRD_Xeq Request<RequestType, MetadataType, AlertType>
 
    if(response_buflen != 0)
    {
-#if 0
-      // How do we handle message boundaries for multi-block responses?
-
-      // Deserialize the response
-
-      ResponseType response;
-
-      if(response.ParseFromArray(response_bufptr, response_buflen))
-      {
-         ResponseCallback(response);
-      }
-      else
-      {
-         ErrorCallback("response.ParseFromArray() failed");
-      }
-#endif
+      // Handle one block of response data
    }
 
    // If there is more data then get it, otherwise clean up
@@ -287,15 +293,14 @@ void Request<RequestType, MetadataType, AlertType>::Alert(XrdSsiRespInfoMsg &ale
 
    AlertType alert;
 
-   if(alert.ParseFromArray(alert_buffer, alert_len))
+   if(!alert.ParseFromArray(alert_buffer, alert_len))
    {
-      AlertCallback(alert);
-   }
-   else
-   {
-      //ErrorCallback("alert.ParseFromArray() failed");
+      PbException e("alert.ParseFromArray() failed");
+      ExceptionHandler(e, alert);
    }
 
+   AlertCallback(alert);
+
    // Recycle the message to free memory
 
    alert_msg.RecycleMsg();