diff --git a/src/ruby/bin/interop/README.md b/src/ruby/bin/interop/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..04020868a4c6fdd095b7ce5fd9bb1979280d0be7
--- /dev/null
+++ b/src/ruby/bin/interop/README.md
@@ -0,0 +1,11 @@
+Interop test protos
+===================
+
+These were generated by a patched version of beefcake and a patched version of
+protoc.
+
+- set up and access of the patched versions is described in ../../README.md
+
+The actual test proto is found in Google3 at
+
+- third_party/stubby/testing/proto/test.proto
diff --git a/src/ruby/bin/interop/interop_client.rb b/src/ruby/bin/interop/interop_client.rb
new file mode 100644
index 0000000000000000000000000000000000000000..309dd337ecdffa6f6d6cfef48345372fc7e43792
--- /dev/null
+++ b/src/ruby/bin/interop/interop_client.rb
@@ -0,0 +1,229 @@
+# Copyright 2014, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#!/usr/bin/env ruby
+# interop_client is a testing tool that accesses a gRPC interop testing
+# server and runs a test on it.
+#
+# Helps validate interoperation b/w different gRPC implementations.
+#
+# Usage: $ path/to/interop_client.rb --server_host=<hostname> \
+#                                    --server_port=<port> \
+#                                    --test_case=<testcase_name>
+
+this_dir = File.expand_path(File.dirname(__FILE__))
+lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib')
+$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
+$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
+
+require 'optparse'
+require 'minitest'
+require 'minitest/assertions'
+
+require 'grpc'
+require 'grpc/generic/client_stub'
+require 'grpc/generic/service'
+
+require 'third_party/stubby/testing/proto/test.pb'
+require 'third_party/stubby/testing/proto/messages.pb'
+
+# loads the certificates used to access the test server securely.
+def load_test_certs
+  this_dir = File.expand_path(File.dirname(__FILE__))
+  data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata')
+  files = ['ca.pem', 'server1.key', 'server1.pem']
+  files.map { |f| File.open(File.join(data_dir, f)).read }
+end
+
+# creates a Credentials from the test certificates.
+def test_creds
+  certs = load_test_certs
+  creds = GRPC::Core::Credentials.new(certs[0])
+end
+
+# creates a test stub that accesses host:port securely.
+def create_stub(host, port)
+  address = "#{host}:#{port}"
+  stub_opts = {
+    :creds => test_creds,
+    GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.com',
+  }
+  logger.info("... connecting securely to #{address}")
+  stub = Grpc::Testing::TestService::Stub.new(address, **stub_opts)
+end
+
+# produces a string of null chars (\0) of length l.
+def nulls(l)
+  raise 'requires #{l} to be +ve' if l < 0
+  [].pack('x' * l)
+end
+
+# defines methods corresponding to each interop test case.
+class NamedTests
+  include Minitest::Assertions
+  include Grpc::Testing
+  include Grpc::Testing::PayloadType
+  attr_accessor :assertions # required by Minitest::Assertions
+
+  def initialize(stub)
+    @assertions = 0  # required by Minitest::Assertions
+    @stub = stub
+  end
+
+  # TESTING
+  # PASSED
+  # FAIL
+  #   ruby server: fails beefcake throws on deserializing the 0-length message
+  def empty_unary
+    resp = @stub.empty_call(Proto2::Empty.new)
+    assert resp.is_a?(Proto::Empty), 'empty_unary: invalid response'
+    p 'OK: empty_unary'
+  end
+
+  # TESTING
+  # PASSED
+  #   ruby server
+  # FAILED
+  def large_unary
+    req_size, wanted_response_size = 271828, 314159
+    payload = Payload.new(:type => COMPRESSABLE, :body => nulls(req_size))
+    req = SimpleRequest.new(:response_type => COMPRESSABLE,
+                            :response_size => wanted_response_size,
+                            :payload => payload)
+    resp = @stub.unary_call(req)
+    assert_equal(wanted_response_size, resp.payload.body.length,
+                 'large_unary: payload had the wrong length')
+    assert_equal(nulls(wanted_response_size), resp.payload.body,
+                 'large_unary: payload content is invalid')
+    p 'OK: large_unary'
+  end
+
+  # TESTING:
+  # PASSED
+  #   ruby server
+  # FAILED
+  def client_streaming
+    msg_sizes = [27182, 8, 1828, 45904]
+    wanted_aggregate_size = 74922
+    reqs = msg_sizes.map do |x|
+      req = Payload.new(:body => nulls(x))
+      StreamingInputCallRequest.new(:payload => req)
+    end
+    resp = @stub.streaming_input_call(reqs)
+    assert_equal(wanted_aggregate_size, resp.aggregated_payload_size,
+                 'client_streaming: aggregate payload size is incorrect')
+    p 'OK: client_streaming'
+   end
+
+  # TESTING:
+  # PASSED
+  #   ruby server
+  # FAILED
+  def server_streaming
+    msg_sizes = [31415, 9, 2653, 58979]
+    response_spec = msg_sizes.map { |s| ResponseParameters.new(:size => s) }
+    req = StreamingOutputCallRequest.new(:response_type => COMPRESSABLE,
+                                         :response_parameters => response_spec)
+    resps = @stub.streaming_output_call(req)
+    resps.each_with_index do |r, i|
+      assert i < msg_sizes.length, 'too many responses'
+      assert_equal(COMPRESSABLE, r.payload.type, 'payload type is wrong')
+      assert_equal(msg_sizes[i], r.payload.body.length,
+                   'payload body #{i} has the wrong length')
+    end
+    p 'OK: server_streaming'
+  end
+
+  # TESTING:
+  # PASSED
+  #   ruby server
+  # FAILED
+  #
+  # TODO(temiola): update this test to stay consistent with the java test's
+  # interpretation of the test spec.
+  def ping_pong
+    req_cls, param_cls= StreamingOutputCallRequest, ResponseParameters  # short
+    msg_sizes = [[27182, 31415], [8, 9], [1828, 2653], [45904, 58979]]
+    reqs = msg_sizes.map do |x|
+      req_size, resp_size = x
+      req_cls.new(:payload => Payload.new(:body => nulls(req_size)),
+                  :response_type => COMPRESSABLE,
+                  :response_parameters => param_cls.new(:size => resp_size))
+    end
+    resps = @stub.full_duplex_call(reqs)
+    resps.each_with_index do |r, i|
+      assert i < msg_sizes.length, 'too many responses'
+      assert_equal(COMPRESSABLE, r.payload.type, 'payload type is wrong')
+      assert_equal(msg_sizes[i][1], r.payload.body.length,
+                   'payload body #{i} has the wrong length')
+    end
+    p 'OK ping_pong'
+  end
+
+end
+
+# validates the the command line options, returning them as a Hash.
+def parse_options
+  options = {
+    'server_host' => nil,
+    'server_port' => nil,
+    'test_case' => nil,
+  }
+  OptionParser.new do |opts|
+    opts.banner = 'Usage: --server_host <server_host> --server_port server_port'
+    opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
+      options['server_host'] = v
+    end
+    opts.on('--server_port SERVER_PORT', 'server port') do |v|
+      options['server_port'] = v
+    end
+    # instance_methods(false) gives only the methods defined in that class
+    test_cases = NamedTests.instance_methods(false).map { |t| t.to_s }
+    test_case_list = test_cases.join(',')
+    opts.on("--test_case CODE", test_cases, {}, "select a test_case",
+            "  (#{test_case_list})") do |v|
+      options['test_case'] = v
+    end
+  end.parse!
+
+  ['server_host', 'server_port', 'test_case'].each do |arg|
+    if options[arg].nil?
+      raise OptionParser::MissingArgument.new("please specify --#{arg}")
+    end
+  end
+  options
+end
+
+def main
+  opts = parse_options
+  stub = create_stub(opts['server_host'], opts['server_port'])
+  NamedTests.new(stub).method(opts['test_case']).call
+end
+
+main
diff --git a/src/ruby/bin/interop/interop_server.rb b/src/ruby/bin/interop/interop_server.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e5c20d44388fa19589d29d33555fe0086f387b6
--- /dev/null
+++ b/src/ruby/bin/interop/interop_server.rb
@@ -0,0 +1,185 @@
+# Copyright 2014, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#!/usr/bin/env ruby
+#
+# interop_server is a Testing app that runs a gRPC interop testing server.
+#
+# It helps validate interoperation b/w gRPC in different environments
+#
+# Helps validate interoperation b/w different gRPC implementations.
+#
+# Usage: $ path/to/interop_server.rb --port
+
+this_dir = File.expand_path(File.dirname(__FILE__))
+lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib')
+$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
+$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
+
+require 'forwardable'
+require 'optparse'
+
+require 'grpc'
+require 'grpc/generic/service'
+require 'grpc/generic/rpc_server'
+
+require 'third_party/stubby/testing/proto/test.pb'
+require 'third_party/stubby/testing/proto/messages.pb'
+
+# loads the certificates by the test server.
+def load_test_certs
+  this_dir = File.expand_path(File.dirname(__FILE__))
+  data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata')
+  files = ['ca.pem', 'server1.key', 'server1.pem']
+  files.map { |f| File.open(File.join(data_dir, f)).read }
+end
+
+# creates a ServerCredentials from the test certificates.
+def test_server_creds
+  certs = load_test_certs
+  server_creds = GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2])
+end
+
+# produces a string of null chars (\0) of length l.
+def nulls(l)
+  raise 'requires #{l} to be +ve' if l < 0
+  [].pack('x' * l)
+end
+
+# A EnumeratorQueue wraps a Queue yielding the items added to it via each_item.
+class EnumeratorQueue
+  extend Forwardable
+  def_delegators :@q, :push
+
+  def initialize(sentinel)
+    @q = Queue.new
+    @sentinel = sentinel
+  end
+
+  def each_item
+    return enum_for(:each_item) unless block_given?
+    loop do
+      r = @q.pop
+      break if r.equal?(@sentinel)
+      raise r if r.is_a?Exception
+      yield r
+    end
+  end
+end
+
+# A runnable implementation of the schema-specified testing service, with each
+# service method implemented as required by the interop testing spec.
+class TestTarget < Grpc::Testing::TestService::Service
+  include Grpc::Testing
+  include Grpc::Testing::PayloadType
+
+  def empty_call(empty, call)
+    Proto::Empty.new
+  end
+
+  def unary_call(simple_req, call)
+    req_size = simple_req.response_size
+    SimpleResponse.new(:payload => Payload.new(:type => COMPRESSABLE,
+                                               :body => nulls(req_size)))
+  end
+
+  def streaming_input_call(call)
+    sizes = call.each_remote_read.map { |x| x.payload.body.length }
+    sum = sizes.inject { |sum,x| sum + x }
+    StreamingInputCallResponse.new(:aggregated_payload_size => sum)
+  end
+
+  def streaming_output_call(req, call)
+    cls = StreamingOutputCallResponse
+    req.response_parameters.map do |p|
+      cls.new(:payload => Payload.new(:type => req.response_type,
+                                      :body => nulls(p.size)))
+    end
+  end
+
+  def full_duplex_call(reqs)
+    # reqs is a lazy Enumerator of the requests sent by the client.
+    q = EnumeratorQueue.new(self)
+    cls = StreamingOutputCallResponse
+    t = Thread.new do
+      begin
+        reqs.each do |req|
+          logger.info("read #{req.inspect}")
+          resp_size = req.response_parameters[0].size
+          resp = cls.new(:payload => Payload.new(:type => req.response_type,
+                                                 :body => nulls(resp_size)))
+          q.push(resp)
+        end
+        logger.info('finished reads')
+        q.push(self)
+      rescue StandardError => e
+        q.push(e)  # share the exception with the enumerator
+      end
+    end
+    q.each_item
+  end
+
+  def half_duplex_call(reqs)
+    # TODO(temiola): clarify the behaviour of the half_duplex_call, it's not
+    # currently used in any tests
+    full_duplex_call(reqs)
+  end
+
+end
+
+# validates the the command line options, returning them as a Hash.
+def parse_options
+  options = {
+    'port' => nil,
+  }
+  OptionParser.new do |opts|
+    opts.banner = 'Usage: --port port'
+    opts.on('--port PORT', 'server port') do |v|
+      options['port'] = v
+    end
+  end.parse!
+
+  if options['port'].nil?
+    raise OptionParser::MissingArgument.new("please specify --port")
+  end
+  options
+end
+
+def main
+  opts = parse_options
+  host = "0.0.0.0:#{opts['port']}"
+  s = GRPC::RpcServer.new(creds: test_server_creds)
+  s.add_http2_port(host, true)
+  logger.info("... running securely on #{host}")
+
+  s.handle(TestTarget)
+  s.run
+end
+
+main
diff --git a/src/ruby/bin/interop/net/proto2/bridge/proto/message_set.pb.rb b/src/ruby/bin/interop/net/proto2/bridge/proto/message_set.pb.rb
new file mode 100755
index 0000000000000000000000000000000000000000..eadd1d4ceb58b9cff98923188e4d6ec249172f69
--- /dev/null
+++ b/src/ruby/bin/interop/net/proto2/bridge/proto/message_set.pb.rb
@@ -0,0 +1,14 @@
+## Generated from net/proto2/bridge/proto/message_set.proto for proto2.bridge
+require 'beefcake'
+
+module Proto2
+  module Bridge
+
+    class MessageSet
+      include Beefcake::Message
+    end
+
+    class MessageSet
+    end
+  end
+end
diff --git a/src/ruby/bin/interop/net/proto2/proto/empty.pb.rb b/src/ruby/bin/interop/net/proto2/proto/empty.pb.rb
new file mode 100755
index 0000000000000000000000000000000000000000..2aa0c765091cc144de0b44286e4a8c27a64cbaa2
--- /dev/null
+++ b/src/ruby/bin/interop/net/proto2/proto/empty.pb.rb
@@ -0,0 +1,12 @@
+## Generated from net/proto2/proto/empty.proto for proto2
+require 'beefcake'
+
+module Proto2
+
+  class Empty
+    include Beefcake::Message
+  end
+
+  class Empty
+  end
+end
diff --git a/src/ruby/bin/interop/third_party/stubby/testing/proto/messages.pb.rb b/src/ruby/bin/interop/third_party/stubby/testing/proto/messages.pb.rb
new file mode 100755
index 0000000000000000000000000000000000000000..9a913f93e58161104241bf39d44fb1fc58fc337f
--- /dev/null
+++ b/src/ruby/bin/interop/third_party/stubby/testing/proto/messages.pb.rb
@@ -0,0 +1,94 @@
+## Generated from third_party/stubby/testing/proto/messages.proto for grpc.testing
+require 'beefcake'
+
+require 'net/proto2/bridge/proto/message_set.pb'
+
+module Grpc
+  module Testing
+
+    module PayloadType
+      COMPRESSABLE = 0
+      UNCOMPRESSABLE = 1
+      RANDOM = 2
+    end
+
+    class Payload
+      include Beefcake::Message
+    end
+
+    class SimpleRequest
+      include Beefcake::Message
+    end
+
+    class SimpleResponse
+      include Beefcake::Message
+    end
+
+    class SimpleContext
+      include Beefcake::Message
+    end
+
+    class StreamingInputCallRequest
+      include Beefcake::Message
+    end
+
+    class StreamingInputCallResponse
+      include Beefcake::Message
+    end
+
+    class ResponseParameters
+      include Beefcake::Message
+    end
+
+    class StreamingOutputCallRequest
+      include Beefcake::Message
+    end
+
+    class StreamingOutputCallResponse
+      include Beefcake::Message
+    end
+
+    class Payload
+      optional :type, PayloadType, 1
+      optional :body, :bytes, 2
+    end
+
+    class SimpleRequest
+      optional :response_type, PayloadType, 1
+      optional :response_size, :int32, 2
+      optional :payload, Payload, 3
+    end
+
+    class SimpleResponse
+      optional :payload, Payload, 1
+      optional :effective_gaia_user_id, :int64, 2
+    end
+
+    class SimpleContext
+      optional :value, :string, 1
+    end
+
+    class StreamingInputCallRequest
+      optional :payload, Payload, 1
+    end
+
+    class StreamingInputCallResponse
+      optional :aggregated_payload_size, :int32, 1
+    end
+
+    class ResponseParameters
+      optional :size, :int32, 1
+      optional :interval_us, :int32, 2
+    end
+
+    class StreamingOutputCallRequest
+      optional :response_type, PayloadType, 1
+      repeated :response_parameters, ResponseParameters, 2
+      optional :payload, Payload, 3
+    end
+
+    class StreamingOutputCallResponse
+      optional :payload, Payload, 1
+    end
+  end
+end
diff --git a/src/ruby/bin/interop/third_party/stubby/testing/proto/test.pb.rb b/src/ruby/bin/interop/third_party/stubby/testing/proto/test.pb.rb
new file mode 100755
index 0000000000000000000000000000000000000000..2e21460fe656201e135e47d3061d7315717be596
--- /dev/null
+++ b/src/ruby/bin/interop/third_party/stubby/testing/proto/test.pb.rb
@@ -0,0 +1,30 @@
+## Generated from third_party/stubby/testing/proto/test.proto for grpc.testing
+require 'beefcake'
+require 'grpc'
+
+require 'third_party/stubby/testing/proto/messages.pb'
+require 'net/proto2/proto/empty.pb'
+
+module Grpc
+  module Testing
+
+    module TestService
+
+      class Service
+        include GRPC::GenericService
+
+        self.marshal_class_method = :encode
+        self.unmarshal_class_method = :decode
+
+        rpc :EmptyCall, Proto2::Empty, Proto2::Empty
+        rpc :UnaryCall, SimpleRequest, SimpleResponse
+        rpc :StreamingOutputCall, StreamingOutputCallRequest, stream(StreamingOutputCallResponse)
+        rpc :StreamingInputCall, stream(StreamingInputCallRequest), StreamingInputCallResponse
+        rpc :FullDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse)
+        rpc :HalfDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse)
+      end
+      Stub = Service.rpc_stub_class
+
+    end
+  end
+end
diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
index c63c80dd58e99850969bc4d256fbdbed60faf55b..b535de49467948fc6f2cc77f8d70498e58b95a21 100755
--- a/src/ruby/grpc.gemspec
+++ b/src/ruby/grpc.gemspec
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
   s.add_dependency 'xray'
   s.add_dependency 'logging', '~> 1.8'
   s.add_dependency 'beefcake', '~> 1.1'
+  s.add_dependency 'minitest', '~> 5.4'  # not a dev dependency, used by the interop tests
 
   s.add_development_dependency "bundler", "~> 1.7"
   s.add_development_dependency "rake", "~> 10.0"
diff --git a/src/ruby/lib/grpc/generic/rpc_desc.rb b/src/ruby/lib/grpc/generic/rpc_desc.rb
index eef886a72f7d69b72cb419bf822fe5802fafd723..b41543e084ec9a985a06de49a5c4a50fd92e416a 100644
--- a/src/ruby/lib/grpc/generic/rpc_desc.rb
+++ b/src/ruby/lib/grpc/generic/rpc_desc.rb
@@ -90,7 +90,7 @@ module GRPC
         # error code and detail message.
         logger.debug("app error: #{active_call}, status:#{e.code}:#{e.details}")
         send_status(active_call, e.code, e.details)
-      rescue CallError => e
+      rescue Core::CallError => e
         # This is raised by GRPC internals but should rarely, if ever happen.
         # Log it, but don't notify the other endpoint..
         logger.warn("failed call: #{active_call}\n#{e}")
@@ -99,7 +99,7 @@ module GRPC
         # event.  Send a status of deadline exceeded
         logger.warn("late call: #{active_call}")
         send_status(active_call, DEADLINE_EXCEEDED, 'late')
-      rescue EventError => e
+      rescue Core::EventError => e
         # This is raised by GRPC internals but should rarely, if ever happen.
         # Log it, but don't notify the other endpoint..
         logger.warn("failed call: #{active_call}\n#{e}")