diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 38eea13..a2d2a7d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,7 @@ changes into our repo.
 
 Some other points to follow:
 
- * Please base your changes on the `master` branch.
+ * Please base your changes on the `main` branch.
 
  * Please follow the [code style requirements](#code-style).
 
diff --git a/README.md b/README.md
index 7bcd196..b4e67fe 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,11 @@ Introduction
 
 Sygnal is a reference Push Gateway for [Matrix](https://matrix.org/).
 
-See <https://matrix.org/docs/spec/client_server/r0.5.0#id134> for a high
+See https://spec.matrix.org/latest/push-gateway-api/#overview for a high
 level overview of how notifications work in Matrix.
 
-<https://matrix.org/docs/spec/push_gateway/r0.1.0> describes the
-protocol that Matrix Home Servers use to send notifications to Push
+The [Matrix Specification](https://spec.matrix.org/latest/push-gateway-api/)
+describes the protocol that Matrix Home Servers use to send notifications to Push
 Gateways such as Sygnal.
 
 
@@ -31,7 +31,7 @@ are to be handled. Each app should be given its own subsection, with the
 key of that subsection being the app's `app_id`. Keys in this section
 take the form of the `app_id`, as specified when setting up a Matrix
 pusher (see
-<https://matrix.org/docs/spec/client_server/r0.5.0#post-matrix-client-r0-pushers-set>).
+https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3pushersset).
 
 See the sample configuration for examples.
 
@@ -103,9 +103,9 @@ If you wish, you can instead configure a HTTP CONNECT proxy in
 Pusher `data` configuration
 ===========================
 
-The following parameters can be specified in the [data]{.title-ref}
+The following parameters can be specified in the `data`
 dictionary which is given when configuring the pusher via
-[POST /_matrix/client/r0/pushers/set](https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set):
+[POST /_matrix/client/v3/pushers/set](https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3pushersset):
 
 - `default_payload`: a dictionary which defines the basic payload to
   be sent to the notification service. Sygnal will merge information
diff --git a/changelog.d/315.misc b/changelog.d/315.misc
new file mode 100644
index 0000000..1dc6d71
--- /dev/null
+++ b/changelog.d/315.misc
@@ -0,0 +1,1 @@
+Don't attempt delivery of notification if we have rejected pushkey.
diff --git a/changelog.d/316.doc b/changelog.d/316.doc
new file mode 100644
index 0000000..9f77e30
--- /dev/null
+++ b/changelog.d/316.doc
@@ -0,0 +1,1 @@
+Update outdated links in `README.md`.
diff --git a/changelog.d/317.doc b/changelog.d/317.doc
new file mode 100644
index 0000000..c3011ff
--- /dev/null
+++ b/changelog.d/317.doc
@@ -0,0 +1,1 @@
+Change `master` to `main` branch in `CONTRIBUTING.md`.
diff --git a/sygnal/apnspushkin.py b/sygnal/apnspushkin.py
index e47374d..c3f18ac 100644
--- a/sygnal/apnspushkin.py
+++ b/sygnal/apnspushkin.py
@@ -311,8 +311,9 @@ class ApnsPushkin(ConcurrencyLimitedPushkin):
             if device.data:
                 default_payload = device.data.get("default_payload", {})
                 if not isinstance(default_payload, dict):
-                    log.error(
-                        "default_payload is malformed, this value must be a dict."
+                    log.warning(
+                        "Rejecting pushkey due to misconfigured default_payload, "
+                        "please ensure that default_payload is a dict."
                     )
                     return [device.pushkey]
 
diff --git a/sygnal/gcmpushkin.py b/sygnal/gcmpushkin.py
index 955f8d0..f725b51 100644
--- a/sygnal/gcmpushkin.py
+++ b/sygnal/gcmpushkin.py
@@ -352,9 +352,13 @@ class GcmPushkin(ConcurrencyLimitedPushkin):
 
             data = GcmPushkin._build_data(n, device)
 
-            # Reject pushkey if default_payload is misconfigured
+            # Reject pushkey(s) if default_payload is misconfigured
             if data is None:
-                failed.append(device.pushkey)
+                log.warning(
+                    "Rejecting pushkey(s) due to misconfigured default_payload, "
+                    "please ensure that default_payload is a dict."
+                )
+                return pushkeys
 
             headers = {
                 "User-Agent": ["sygnal"],
@@ -432,7 +436,7 @@ class GcmPushkin(ConcurrencyLimitedPushkin):
             if isinstance(default_payload, dict):
                 data.update(default_payload)
             else:
-                logger.error(
+                logger.warning(
                     "default_payload was misconfigured, this value must be a dict."
                 )
                 return None

diff --git a/tests/test_gcm.py b/tests/test_gcm.py
index d705751..dcb685b 100644
--- a/tests/test_gcm.py
+++ b/tests/test_gcm.py
@@ -142,7 +142,7 @@ class GcmTestCase(testutils.TestCase):
         )
 
         self.assertEqual(resp, {"rejected": ["badpayload"]})
-        self.assertEqual(gcm.num_requests, 1)
+        self.assertEqual(gcm.num_requests, 0)
 
     def test_rejected(self):
         """

"default_payload was misconfigured, this value must be a dict" error is noisy
**Describe the bug**
As of https://github.com/matrix-org/sygnal/pull/292 we now check that `default_payload` is a dictionary before interacting with it. `default_payload` is a user-supplied dictionary which is set when a client sets up their pusher via [`POST /_matrix/client/v3/pushers/set`](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3pushersset).

While we now successfully catch when clients set this value incorrectly, raising an error means that this we get notified via Sentry when it happens - and as this data is user-supplied, we can't really do much about it, making the error noise.

This issue proposes to downgrade the log from an error to a warning. Such instances will then still be logged, but not show up in Sentry/potentially alarm sysadmins.
tests/test_gcm.py::GcmTestCase::test_misformed_default_payload_rejected
tests/test_gcm.py::GcmTestCase::test_batching tests/test_gcm.py::GcmTestCase::test_batching_individual_failure tests/test_gcm.py::GcmTestCase::test_expected tests/test_gcm.py::GcmTestCase::test_expected_with_default_payload tests/test_gcm.py::GcmTestCase::test_fcm_options tests/test_gcm.py::GcmTestCase::test_rejected