diff --git a/sms.routing.yml b/sms.routing.yml
index f2e5858..a613a88 100644
--- a/sms.routing.yml
+++ b/sms.routing.yml
@@ -89,4 +89,4 @@ sms.process_delivery_report:
   defaults:
     _controller: '\Drupal\sms\DeliveryReportController::processDeliveryReport'
   requirements:
-    _access: 'TRUE'
+    _sms_gateway_supports_pushed_reports: 'TRUE'
diff --git a/sms.services.yml b/sms.services.yml
index 9c4522c..ec3244b 100644
--- a/sms.services.yml
+++ b/sms.services.yml
@@ -18,3 +18,7 @@ services:
     arguments: ['@event_dispatcher', '@config.factory']
     tags:
       - { name: event_subscriber }
+  access_check.sms.gateway_supports_pushed_reports:
+    class: Drupal\sms\Access\SupportsPushedReportsAccessCheck
+    tags:
+      - { name: access_check, applies_to: _sms_gateway_supports_pushed_reports }
diff --git a/src/Access/SupportsPushedReportsAccessCheck.php b/src/Access/SupportsPushedReportsAccessCheck.php
new file mode 100644
index 0000000..1358b40
--- /dev/null
+++ b/src/Access/SupportsPushedReportsAccessCheck.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\sms\Access;
+
+use Symfony\Component\Routing\Route;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\sms\Entity\SmsGatewayInterface;
+
+/**
+ * Checks if gateway supports pushed reports.
+ */
+class SupportsPushedReportsAccessCheck implements AccessInterface {
+
+  /**
+   * Checks if the gateway supports pushed reports
+   */
+  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, SmsGatewayInterface $sms_gateway) {
+    return AccessResult::allowedIf($sms_gateway->supportsReportsPush())
+      ->addCacheContexts(['route']);
+  }
+
+}
diff --git a/src/Annotation/SmsGateway.php b/src/Annotation/SmsGateway.php
index 9e5b6f0..f319f72 100644
--- a/src/Annotation/SmsGateway.php
+++ b/src/Annotation/SmsGateway.php
@@ -50,4 +50,22 @@ class SmsGateway extends Plugin {
    */
   protected $schedule_aware;
 
+  /**
+   * Whether the gateway can pull reports.
+   *
+   * @see \Drupal\sms\Entity\SmsGatewayInterface::supportsReportsPull()
+   *
+   * @var boolean
+   */
+  protected $reports_pull;
+
+  /**
+   * Whether the gateway can handle reports pushed to the site.
+   *
+   * @see \Drupal\sms\Entity\SmsGatewayInterface::supportsReportsPush()
+   *
+   * @var boolean
+   */
+  protected $reports_push;
+
 }
diff --git a/src/Entity/SmsGateway.php b/src/Entity/SmsGateway.php
index ade32ed..78e4905 100644
--- a/src/Entity/SmsGateway.php
+++ b/src/Entity/SmsGateway.php
@@ -203,4 +203,22 @@ class SmsGateway extends ConfigEntityBase implements SmsGatewayInterface, Entity
     return !empty($definition['schedule_aware']);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function supportsReportsPull() {
+    $definition = $this->getPlugin()
+      ->getPluginDefinition();
+    return isset($definition['reports_pull']) ? (boolean) $definition['reports_pull'] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function supportsReportsPush() {
+    $definition = $this->getPlugin()
+      ->getPluginDefinition();
+    return isset($definition['reports_push']) ? (boolean) $definition['reports_push'] : FALSE;
+  }
+
 }
diff --git a/src/Entity/SmsGatewayInterface.php b/src/Entity/SmsGatewayInterface.php
index ccc6e2c..96515bd 100644
--- a/src/Entity/SmsGatewayInterface.php
+++ b/src/Entity/SmsGatewayInterface.php
@@ -91,4 +91,24 @@ interface SmsGatewayInterface extends ConfigEntityInterface {
    */
   public function isScheduleAware();
 
+  /**
+   * Gets whether this gateway can pull reports.
+   *
+   * @return boolean
+   *   Whether this gateway can pull reports.
+   *
+   * @see \Drupal\sms\Annotation\SmsGateway::reports_pull
+   */
+  public function supportsReportsPull();
+
+  /**
+   * Gets whether this gateway can handle reports pushed to the site.
+   *
+   * @return boolean
+   *   Whether this gateway can handle reports pushed to the site.
+   *
+   * @see \Drupal\sms\Annotation\SmsGateway::reports_push
+   */
+  public function supportsReportsPush();
+
 }
diff --git a/src/Form/SmsGatewayForm.php b/src/Form/SmsGatewayForm.php
index d5b4fa3..e238085 100644
--- a/src/Form/SmsGatewayForm.php
+++ b/src/Form/SmsGatewayForm.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\sms\Form;
 
+use Drupal\Core\Access\AccessManagerInterface;
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
@@ -23,6 +24,13 @@ use Drupal\sms\Direction;
 class SmsGatewayForm extends EntityForm {
 
   /**
+   * The access manager service.
+   *
+   * @var \Drupal\Core\Access\AccessManagerInterface
+   */
+  protected $accessManager;
+
+  /**
    * @var \Drupal\Core\Entity\Query\QueryFactory
    */
   protected $entityQueryFactory;
@@ -35,10 +43,13 @@ class SmsGatewayForm extends EntityForm {
   protected $gatewayManager;
 
   /**
+   * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
+   *   The access manager.
    * @param \Drupal\sms\Plugin\SmsGatewayPluginManagerInterface $gateway_manager
    *   The gateway manager service.
    */
-  public function __construct(QueryFactory $query_factory, SmsGatewayPluginManagerInterface $gateway_manager) {
+  public function __construct(AccessManagerInterface $access_manager, QueryFactory $query_factory, SmsGatewayPluginManagerInterface $gateway_manager) {
+    $this->accessManager = $access_manager;
     $this->entityQueryFactory = $query_factory;
     $this->gatewayManager = $gateway_manager;
   }
@@ -48,6 +59,7 @@ class SmsGatewayForm extends EntityForm {
    */
   public static function create(ContainerInterface $container) {
     return new static(
+      $container->get('access_manager'),
       $container->get('entity.query'),
       $container->get('plugin.manager.sms_gateway')
     );
@@ -148,11 +160,16 @@ class SmsGatewayForm extends EntityForm {
     ];
 
     if (!$sms_gateway->isNew()) {
-      $form['delivery_report_path'] = [
-        '#type' => 'item',
-        '#title' => $this->t('Delivery report URL'),
-        '#markup' => Url::fromRoute('sms.process_delivery_report', ['sms_gateway' => $sms_gateway->id()], ['absolute' => TRUE])->toString(),
-      ];
+      $url = Url::fromRoute('sms.process_delivery_report', ['sms_gateway' => $sms_gateway->id()])->setAbsolute();
+      $access = $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters());
+      if ($access) {
+        $form['delivery_report_path'] = [
+          '#type' => 'item',
+          '#title' => $this->t('Delivery report URL'),
+          '#markup' => $url->toString(),
+        ];
+      }
+
       $instance = $sms_gateway->getPlugin();
       $form += $instance->buildConfigurationForm($form, $form_state);
     }
diff --git a/src/Plugin/SmsGatewayPluginInterface.php b/src/Plugin/SmsGatewayPluginInterface.php
index 73823e6..c92fd8a 100644
--- a/src/Plugin/SmsGatewayPluginInterface.php
+++ b/src/Plugin/SmsGatewayPluginInterface.php
@@ -147,10 +147,11 @@ interface SmsGatewayPluginInterface extends ConfigurablePluginInterface, PluginF
   public function parseDeliveryReports(Request $request, Response $response);
 
   /**
-   * Gets the latest available delivery reports from the SMS gateway server.
+   * Gets delivery reports from the gateway.
    *
-   * @param string[]|null $message_ids
-   *   The list of specific message_ids to poll. NULL to get all reports.
+   * @param string[]|NULL $message_ids
+   *   A list of specific message ID's to pull, or NULL to get any reports which
+   *   have not been requested previously.
    *
    * @return \Drupal\sms\Message\SmsDeliveryReportInterface[]
    *   An array of the delivery reports which have been pulled.
diff --git a/src/Tests/SmsFrameworkGatewayAdminTest.php b/src/Tests/SmsFrameworkGatewayAdminTest.php
index f5e964c..cea0864 100644
--- a/src/Tests/SmsFrameworkGatewayAdminTest.php
+++ b/src/Tests/SmsFrameworkGatewayAdminTest.php
@@ -135,6 +135,11 @@ class SmsFrameworkGatewayAdminTest extends SmsFrameworkWebTestBase {
     $this->assertFieldByName('retention_duration_incoming', '0');
     $this->assertFieldByName('retention_duration_outgoing', '0');
 
+    // Memory gateway supports pushed reports, so the URL should display.
+    $url = Url::fromRoute('sms.process_delivery_report', ['sms_gateway' => $test_gateway->id()])->setAbsolute();
+    $this->assertRaw(t('Delivery report URL'));
+    $this->assertRaw($url->toString(), 'Delivery report URL is visible');
+
     // Memory gateway has a decoy configuration form.
     $edit = [
       'widget' => $this->randomString(),
@@ -164,6 +169,22 @@ class SmsFrameworkGatewayAdminTest extends SmsFrameworkWebTestBase {
   }
 
   /**
+   * Tests a gateway edit form does not display delivery report URL.
+   */
+  public function testGatewayEditNoDeliveryUrl() {
+    $this->drupalLogin($this->drupalCreateUser(['administer smsframework']));
+    $test_gateway = $this->createMemoryGateway(['plugin' => 'capabilities_default']);
+
+    $this->drupalGet(Url::fromRoute('entity.sms_gateway.edit_form', [
+      'sms_gateway' => $test_gateway->id(),
+    ]));
+    $this->assertResponse(200);
+    $this->assertRaw('Edit gateway');
+
+    $this->assertNoRaw(t('Delivery report URL'));
+  }
+
+    /**
    * Tests deleting a gateway.
    */
   public function testGatewayDelete() {
diff --git a/tests/modules/sms_test_gateway/config/schema/sms_test_gateway.schema.yml b/tests/modules/sms_test_gateway/config/schema/sms_test_gateway.schema.yml
index 8875827..7abb3a9 100644
--- a/tests/modules/sms_test_gateway/config/schema/sms_test_gateway.schema.yml
+++ b/tests/modules/sms_test_gateway/config/schema/sms_test_gateway.schema.yml
@@ -29,3 +29,10 @@ sms_gateway.settings.memory_schedule_aware:
   mapping:
     gateway_id:
       type: string
+
+sms_gateway.settings.capabilities_default:
+  type: sms_gateway_settings
+  label: 'Default annotation capabilities'
+  mapping:
+    gateway_id:
+      type: string
diff --git a/tests/modules/sms_test_gateway/src/Plugin/SmsGateway/DefaultCapabilities.php b/tests/modules/sms_test_gateway/src/Plugin/SmsGateway/DefaultCapabilities.php
new file mode 100644
index 0000000..abfb760
--- /dev/null
+++ b/tests/modules/sms_test_gateway/src/Plugin/SmsGateway/DefaultCapabilities.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\sms_test_gateway\Plugin\SmsGateway;
+
+use Drupal\sms\Plugin\SmsGatewayPluginBase;
+use Drupal\sms\Message\SmsMessageInterface;
+
+/**
+ * Defines a gateway for testing default capabilities defined by annotation.
+ *
+ * This gateway does not provide any annotation details other than required
+ * properties: 'id' and 'label'.
+ *
+ * @SmsGateway(
+ *   id = "capabilities_default",
+ *   label = @Translation("Default annotation capabilities")
+ * )
+ */
+class DefaultCapabilities extends SmsGatewayPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function send(SmsMessageInterface $sms) {
+  }
+
+}
diff --git a/tests/modules/sms_test_gateway/src/Plugin/SmsGateway/Memory.php b/tests/modules/sms_test_gateway/src/Plugin/SmsGateway/Memory.php
index 8a8f150..55d293d 100644
--- a/tests/modules/sms_test_gateway/src/Plugin/SmsGateway/Memory.php
+++ b/tests/modules/sms_test_gateway/src/Plugin/SmsGateway/Memory.php
@@ -27,6 +27,8 @@ use Symfony\Component\HttpFoundation\Response;
  *   label = @Translation("Memory"),
  *   outgoing_message_max_recipients = -1,
  *   schedule_aware = FALSE,
+ *   reports_pull = TRUE,
+ *   reports_push = TRUE,
  * )
  */
 class Memory extends SmsGatewayPluginBase implements SmsGatewayPluginIncomingInterface{
@@ -134,6 +136,13 @@ class Memory extends SmsGatewayPluginBase implements SmsGatewayPluginIncomingInt
   }
 
   /**
+   * @inheritdoc
+   */
+  public function getDeliveryReports(array $message_ids = NULL) {
+    return [];
+  }
+
+  /**
    * Generates random delivery reports for each of the recipients of a message.
    *
    * @param \Drupal\sms\Message\SmsMessageInterface $sms_message
diff --git a/tests/src/Kernel/SmsFrameworkGatewayEntityTest.php b/tests/src/Kernel/SmsFrameworkGatewayEntityTest.php
index 06f2129..044e398 100644
--- a/tests/src/Kernel/SmsFrameworkGatewayEntityTest.php
+++ b/tests/src/Kernel/SmsFrameworkGatewayEntityTest.php
@@ -72,13 +72,56 @@ class SmsFrameworkGatewayEntityTest extends SmsFrameworkKernelBase {
   }
 
   /**
+   * Tests 'supports pushed reports' annotation custom value.
+   */
+  public function testSupportsReportsPushCustom() {
+    $gateway = $this->createGateway([
+      'plugin' => 'memory',
+    ]);
+    $this->assertTrue($gateway->supportsReportsPush());
+  }
+
+  /**
+   * Tests 'supports credit balance' annotation default value.
+   */
+  public function testSupportsReportsPushDefault() {
+    $gateway = $this->createGateway([
+      'plugin' => 'capabilities_default',
+    ]);
+    $this->assertFalse($gateway->supportsReportsPush());
+  }
+
+  /**
+   * Tests 'supports pulling reports' annotation custom value.
+   */
+  public function testSupportsReportsPullCustom() {
+    $gateway = $this->createGateway([
+      'plugin' => 'memory',
+    ]);
+    $this->assertTrue($gateway->supportsReportsPull());
+  }
+
+  /**
+   * Tests 'supports pulling balance' annotation default value.
+   */
+  public function testSupportsReportsPullDefault() {
+    $gateway = $this->createGateway([
+      'plugin' => 'capabilities_default',
+    ]);
+    $this->assertFalse($gateway->supportsReportsPull());
+  }
+
+  /**
    * Create a new gateway.
    *
+   * @param array $values
+   *   Custom values to pass to the gateway.
+   *
    * @return \Drupal\sms\Entity\SmsGatewayInterface
    *   An unsaved gateway config entity.
    */
-  protected function createGateway() {
-    return SmsGateway::create([
+  protected function createGateway($values = []) {
+    return SmsGateway::create($values + [
       'plugin' => 'memory',
     ]);
   }
diff --git a/tests/src/Kernel/SmsFrameworkPushedDeliveryReportTest.php b/tests/src/Kernel/SmsFrameworkPushedDeliveryReportTest.php
new file mode 100644
index 0000000..716d875
--- /dev/null
+++ b/tests/src/Kernel/SmsFrameworkPushedDeliveryReportTest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\Tests\sms\Kernel;
+
+use Drupal\Core\Url;
+
+/**
+ * Tests pushing delivery reports to the site.
+ *
+ * @group SMS Framework
+ */
+class SmsFrameworkPushedDeliveryReportTest extends SmsFrameworkKernelBase {
+
+  /**
+   * @inheritdoc
+   */
+  public static $modules = ['system', 'sms', 'entity_test', 'user', 'field', 'telephone', 'dynamic_entity_reference', 'sms_test_gateway'];
+
+  /**
+   * The access manager service.
+   *
+   * @var \Drupal\Core\Access\AccessManagerInterface
+   */
+  protected $accessManager;
+
+  /**
+   * @inheritdoc
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->accessManager = $this->container->get('access_manager');
+  }
+
+  /**
+   * Tests route access delivery report URL for gateway with pushed reports.
+   */
+  public function testDeliveryReportRoute() {
+    $gateway = $this->createMemoryGateway();
+    $url = Url::fromRoute('sms.process_delivery_report', ['sms_gateway' => $gateway->id()]);
+    $access = $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters());
+    $this->assertTrue($access, 'Access to delivery report URL is allowed.');
+  }
+
+  /**
+   * Tests route access delivery report URL for gateway without pushed reports.
+   */
+  public function testDeliveryReportRouteNoSupportPush() {
+    $gateway = $this->createMemoryGateway(['plugin' => 'capabilities_default']);
+    $url = Url::fromRoute('sms.process_delivery_report', ['sms_gateway' => $gateway->id()]);
+    $access = $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters());
+    $this->assertFalse($access, 'Access to delivery report URL is forbidden.');
+  }
+
+}
