diff --git a/app/models/agents/twilio_receive_text_agent.rb b/app/models/agents/twilio_receive_text_agent.rb
new file mode 100644
index 00000000..b3c7d92c
--- /dev/null
+++ b/app/models/agents/twilio_receive_text_agent.rb
@@ -0,0 +1,100 @@
+module Agents
+ class TwilioReceiveTextAgent < Agent
+ cannot_be_scheduled!
+ cannot_receive_events!
+
+ gem_dependency_check { defined?(Twilio) }
+
+ description do <<-MD
+ The Twilio Receive Text Agent receives text messages from Twilio and emits them as events.
+
+ #{'## Include `twilio-ruby` in your Gemfile to use this Agent!' if dependencies_missing?}
+
+ In order to create events with this agent, configure Twilio to send POST requests to:
+
+ ```
+ #{post_url}
+ ```
+
+ #{'The placeholder symbols above will be replaced by their values once the agent is saved.' unless id}
+
+ Options:
+
+ * `server_url` must be set to the URL of your
+ Huginn installation (probably "https://#{ENV['DOMAIN']}"), which must be web-accessible. Be sure to set http/https correctly.
+
+ * `account_sid` and `auth_token` are your Twilio account credentials. `auth_token` must be the primary auth token for your Twilio accout.
+
+ * If `reply_text` is set, it's contents will be sent back as a confirmation text.
+
+ * `expected_receive_period_in_days` - How often you expect to receive events this way. Used to determine if the agent is working.
+ MD
+ end
+
+ def default_options
+ {
+ 'account_sid' => 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ 'auth_token' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ 'server_url' => "https://#{ENV['DOMAIN'].presence || example.com}",
+ 'reply_text' => '',
+ "expected_receive_period_in_days" => 1
+ }
+ end
+
+ def validate_options
+ unless options['account_sid'].present? && options['auth_token'].present? && options['server_url'].present? && options['expected_receive_period_in_days'].present?
+ errors.add(:base, 'account_sid, auth_token, server_url, and expected_receive_period_in_days are all required')
+ end
+ end
+
+ def working?
+ event_created_within?(interpolated['expected_receive_period_in_days']) && !recent_error_logs?
+ end
+
+ def post_url
+ if interpolated['server_url'].present?
+ "#{interpolated['server_url']}/users/#{user.id}/web_requests/#{id || ':id'}/sms-endpoint"
+ else
+ "https://#{ENV['DOMAIN']}/users/#{user.id}/web_requests/#{id || ':id'}/sms-endpoint"
+ end
+ end
+
+ def receive_web_request(request)
+ params = request.params.except(:action, :controller, :agent_id, :user_id, :format)
+ method = request.method_symbol.to_s
+ headers = request.headers
+
+ # check the last url param: 'secret'
+ secret = params.delete('secret')
+ return ["Not Authorized", 401] unless secret == "sms-endpoint"
+
+ signature = headers['HTTP_X_TWILIO_SIGNATURE']
+
+ # validate from twilio
+ @validator ||= Twilio::Util::RequestValidator.new interpolated['auth_token']
+ if !@validator.validate(post_url, params, signature)
+ error("Twilio Signature Failed to Validate\n\n"+
+ "URL: #{post_url}\n\n"+
+ "POST params: #{params.inspect}\n\n"+
+ "Signature: #{signature}"
+ )
+ return ["Not authorized", 401]
+ end
+
+ if create_event(payload: params)
+ response = Twilio::TwiML::Response.new do |r|
+ if interpolated['reply_text'].present?
+ r.Message interpolated['reply_text']
+ end
+ end
+ return [response.text, 201, "text/xml"]
+ else
+ return ["Bad request", 400]
+ end
+ end
+
+ # def client
+ # @client ||= Twilio::REST::Client.new interpolated['account_sid'], interpolated['auth_token']
+ # end
+ end
+end
diff --git a/spec/models/agents/twilio_receive_text_agent_spec.rb b/spec/models/agents/twilio_receive_text_agent_spec.rb
new file mode 100644
index 00000000..5622e7ad
--- /dev/null
+++ b/spec/models/agents/twilio_receive_text_agent_spec.rb
@@ -0,0 +1,99 @@
+require 'rails_helper'
+
+# Twilio Params
+# https://www.twilio.com/docs/api/twiml/sms/twilio_request
+# url: https://b924379f.ngrok.io/users/1/web_requests/7/sms-endpoint
+# params: {"ToCountry"=>"US", "ToState"=>"NY", "SmsMessageSid"=>"SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "NumMedia"=>"0", "ToCity"=>"NEW YORK", "FromZip"=>"48342", "SmsSid"=>"SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "FromState"=>"MI", "SmsStatus"=>"received", "FromCity"=>"PONTIAC", "Body"=>"Lol", "FromCountry"=>"US", "To"=>"+1347555555", "ToZip"=>"10016", "NumSegments"=>"1", "MessageSid"=>"SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "AccountSid"=>"ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "From"=>"+12485551111", "ApiVersion"=>"2010-04-01"}
+# signature: K29NMD9+v5/QLzbdGZW/DRGyxNU=
+
+describe Agents::TwilioReceiveTextAgent do
+ before do
+ stub.any_instance_of(Twilio::Util::RequestValidator).validate { true }
+ end
+
+ let(:payload) {
+ {
+ "ToCountry"=>"US",
+ "ToState"=>"NY",
+ "SmsMessageSid"=>"SMxxxxxxxxxxxxxxxx",
+ "NumMedia"=>"0",
+ "ToCity"=>"NEW YORK",
+ "FromZip"=>"48342",
+ "SmsSid"=>"SMxxxxxxxxxxxxxxxx",
+ "FromState"=>"MI",
+ "SmsStatus"=>"received",
+ "FromCity"=>"PONTIAC",
+ "Body"=>"Hy ",
+ "FromCountry"=>"US",
+ "To"=>"+1347555555",
+ "ToZip"=>"10016",
+ "NumSegments"=>"1",
+ "MessageSid"=>"SMxxxxxxxxxxxxxxxx",
+ "AccountSid"=>"ACxxxxxxxxxxxxxxxx",
+ "From"=>"+12485551111",
+ "ApiVersion"=>"2010-04-01"}
+ }
+
+ describe 'receive_twilio_text_message' do
+ before do
+ @agent = Agents::TwilioReceiveTextAgent.new(
+ :name => 'twilioreceive',
+ :options => { :account_sid => 'x',
+ :auth_token => 'x',
+ :server_url => 'http://example.com',
+ :expected_receive_period_in_days => 1
+ }
+ )
+ @agent.user = users(:bob)
+ @agent.save!
+ end
+
+ it 'should create event upon receiving request' do
+
+ request = ActionDispatch::Request.new({
+ 'action_dispatch.request.request_parameters' => payload.merge({"secret" => "sms-endpoint"}),
+ 'REQUEST_METHOD' => "POST",
+ 'HTTP_ACCEPT' => 'application/xml',
+ 'HTTP_X_TWILIO_SIGNATURE' => "HpS7PBa1Agvt4OtO+wZp75IuQa0="
+ })
+
+ out = nil
+ expect {
+ out = @agent.receive_web_request(request)
+ }.to change { Event.count }.by(1)
+ expect(out).to eq(["", 201, "text/xml"])
+ expect(Event.last.payload).to eq(payload)
+ end
+ end
+
+ describe 'receive_twilio_text_message and send a response' do
+ before do
+ @agent = Agents::TwilioReceiveTextAgent.new(
+ :name => 'twilioreceive',
+ :options => { :account_sid => 'x',
+ :auth_token => 'x',
+ :server_url => 'http://example.com',
+ :reply_text => "thanks!",
+ :expected_receive_period_in_days => 1
+ }
+ )
+ @agent.user = users(:bob)
+ @agent.save!
+ end
+
+ it 'should create event and send back TwiML Message if reply_text is set' do
+ out = nil
+ request = ActionDispatch::Request.new({
+ 'action_dispatch.request.request_parameters' => payload.merge({"secret" => "sms-endpoint"}),
+ 'REQUEST_METHOD' => "POST",
+ 'HTTP_ACCEPT' => 'application/xml',
+ 'HTTP_X_TWILIO_SIGNATURE' => "HpS7PBa1Agvt4OtO+wZp75IuQa0="
+ })
+ expect {
+ out = @agent.receive_web_request(request)
+ }.to change { Event.count }.by(1)
+ expect(out).to eq(["thanks!", 201, "text/xml"])
+ expect(Event.last.payload).to eq(payload)
+ end
+ end
+end