diff --git a/composer.json b/composer.json
index 1e60445..7e527e5 100644
--- a/composer.json
+++ b/composer.json
@@ -30,5 +30,8 @@
     "drupal/core": "^8.0",
     "drupal/smsframework": "^1.0@beta",
     "twilio/sdk": "5.4.*"
+  },
+  "suggest": {
+    "drupal/smsframework": "Provides integration with SMS Framework"
   }
 }
diff --git a/src/Plugin/SmsGateway/Twilio.php b/src/Plugin/SmsGateway/Twilio.php
index 44c0b70..a248fa9 100644
--- a/src/Plugin/SmsGateway/Twilio.php
+++ b/src/Plugin/SmsGateway/Twilio.php
@@ -3,12 +3,21 @@
 namespace Drupal\sms_twilio\Plugin\SmsGateway;

 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Logger\LoggerChannel;
+use Drupal\sms\Direction;
+use Drupal\sms\Entity\SmsGatewayInterface;
 use Drupal\sms\Message\SmsDeliveryReport;
+use Drupal\sms\Message\SmsMessage;
 use Drupal\sms\Message\SmsMessageResultStatus;
 use Drupal\sms\Message\SmsMessageReportStatus;
 use Drupal\sms\Plugin\SmsGatewayPluginBase;
 use Drupal\sms\Message\SmsMessageInterface;
 use Drupal\sms\Message\SmsMessageResult;
+use Drupal\sms\SmsProcessingResponse;
+use Drupal\sms_twilio\Utility\TwilioMedia;
+use Drupal\sms_twilio\Utility\TwilioValidation;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
 use Twilio\Exceptions\RestException;
 use Twilio\Rest\Client;

@@ -18,6 +27,8 @@
  *   label = @Translation("Twilio"),
  *   outgoing_message_max_recipients = 1,
  *   reports_push = TRUE,
+ *   incoming = TRUE,
+ *   incoming_route = TRUE
  * )
  */
 class Twilio extends SmsGatewayPluginBase {
@@ -108,6 +119,7 @@ public function send(SmsMessageInterface $sms_message) {
     ];

     $report = new SmsDeliveryReport();
+    $report->setRecipient($recipient);
     try {
       $message = $client->messages->create($recipient, $options);
       $report->setStatus(SmsMessageReportStatus::QUEUED);
@@ -130,7 +142,8 @@ public function send(SmsMessageInterface $sms_message) {
         // 21408: Account doesn't have the international permission.
         //       (Test recipient: +15005550003)
         $result->setError(SmsMessageResultStatus::ACCOUNT_ERROR);
-        $result->setStatusMessage($message);
+        $report->setStatus(SmsMessageReportStatus::ERROR);
+        $report->setStatusMessage($message);
       }
       else {
         $report->setStatus(SmsMessageReportStatus::ERROR);
@@ -139,10 +152,70 @@ public function send(SmsMessageInterface $sms_message) {
     }

     if ($report->getStatus()) {
-      $result->setReports([$report]);
+      $result->addReport($report);
     }

     return $result;
   }

+  /**
+   * Validates the webhook request and creates an SMS message object.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request The current request.
+   *
+   * @return \Drupal\sms\Message\SmsMessage The parsed message.
+   */
+  protected function buildIncomingFromRequest(Request $request) {
+    $result = new SmsMessageResult();
+    $params = $request->request->all();
+    $report = (new SmsDeliveryReport())
+      ->setRecipient($params['To'])
+      ->setStatus(SmsMessageReportStatus::DELIVERED);
+    $sms = (new SmsMessage())
+      ->setMessage(trim($params['Body']))
+      ->setDirection(Direction::INCOMING)
+      ->setOption('data', $params)
+      ->setSenderNumber($params['From'])
+      ->addRecipients([$params['To']]);
+    if ($files = TwilioMedia::processMedia($params)) {
+      $sms->setOption('media', $files);
+    }
+    try {
+      TwilioValidation::validateIncoming($request, $this);
+    }
+    catch (\Exception $e) {
+      $report->setStatus(SmsMessageReportStatus::REJECTED);
+      $report->setStatusMessage($e->getMessage());
+      $result->setError($e->getCode());
+      $result->setErrorMessage($e->getMessage());
+    }
+    $result->addReport($report);
+    $sms->setResult($result);
+
+    return $sms;
+  }
+
+  /**
+   * Callback for processing incoming messages.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request The active request.
+   * @param \Drupal\sms\Entity\SmsGatewayInterface $gateway The SMS gateway.
+   *
+   * @return \Drupal\sms\SmsProcessingResponse The processing response.
+   */
+  public function processIncoming(Request $request, SmsGatewayInterface $sms_gateway) {
+    $task = new SmsProcessingResponse();
+    $sms = $this->buildIncomingFromRequest($request);
+    $sms->setGateway($sms_gateway);
+    // Replies should be handled in implementing code.
+    $response = new Response();
+    if ($sms->getResult()->getError()) {
+      $response = new Response($sms->getResult()->getErrorMessage(), $sms->getResult()->getError());
+    }
+    $task->setMessages([$sms]);
+    $task->setResponse($response);
+
+    return $task;
+  }
+
 }
diff --git a/src/Utility/TwilioMedia.php b/src/Utility/TwilioMedia.php
new file mode 100644
index 0000000..eab7baa
--- /dev/null
+++ b/src/Utility/TwilioMedia.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\sms_twilio\Utility;
+
+/**
+ * Class TwilioMedia
+ *
+ * Contains methods for working with media sent by SMS.
+ *
+ * @package Drupal\sms_twilio\Utility
+ */
+class TwilioMedia {
+
+  /**
+   * Helper function for processing attached SMS/MMS media.
+   *
+   * @param array $params The original payload from Twilio.
+   * @return array An array of files, with url and content-type keys.
+   */
+  public static function processMedia(array $params) {
+    $i = 0;
+    $files = [];
+    while ($i < $params['NumMedia']) {
+      $files[] = [
+        'url' => $params['MediaUrl' . $i],
+        'content-type' => $params['MediaContentType' . $i],
+      ];
+      $i++;
+    }
+    return $files;
+  }
+}
diff --git a/src/Utility/TwilioValidation.php b/src/Utility/TwilioValidation.php
new file mode 100644
index 0000000..47f651e
--- /dev/null
+++ b/src/Utility/TwilioValidation.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\sms_twilio\Utility;
+
+use Drupal\Core\Url;
+use Drupal\sms\Plugin\SmsGatewayPluginInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Twilio\Security\RequestValidator;
+
+/**
+ * Class TwilioValidation
+ *
+ * Methods for validating incoming webhook POST events from Twilio.
+ *
+ * @package Drupal\sms_twilio\Utility
+ */
+class TwilioValidation {
+
+  /**
+   * Validate an incoming message using Twilio SDK
+   * @see https://www.twilio.com/docs/api/security
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request The request object.
+   * @param \Drupal\Component\Plugin\ConfigurablePluginInterface The Twilio plugin.
+   *
+   * @return boolean TRUE if the request validates, FALSE if not.
+   */
+  public static function validateIncoming(Request $request, SmsGatewayPluginInterface $sms_gateway) {
+    $url = Url::fromRoute('sms.incoming.receive.' . $sms_gateway->getPluginId())
+      ->setAbsolute()
+      ->toString();
+    $signature = $request->headers->get('x-twilio-signature');
+    $token = $sms_gateway->getConfiguration()['auth_token'];
+
+    $validator = new RequestValidator($token);
+    $valid = $validator->validate($signature, $url, $request->request->all());
+
+    if (!$valid) {
+      throw new AccessDeniedHttpException('Signature validation failed.');
+    }
+
+    return $valid;
+  }
+
+}
