diff --git a/CHANGELOG.md b/CHANGELOG.md
index 35c554ca..f6af79b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
 ### Improvements
 - Add /api/v1/badges/ endpoint (#552)
 - Add ability to edit existing email, Signal, SMS, WhatsApp integrations
+- Add new ping URL format: /{ping_key}/{slug} (#491)
 
 ### Bug Fixes
 - Add handling for non-latin-1 characters in webhook headers
diff --git a/hc/accounts/models.py b/hc/accounts/models.py
index a615db8e..7886e0ef 100644
--- a/hc/accounts/models.py
+++ b/hc/accounts/models.py
@@ -338,18 +338,6 @@ class Project(models.Model):
     def num_checks_available(self):
         return self.owner_profile.num_checks_available()
 
-    def set_api_keys(self):
-        def pick(nbytes=24):
-            while True:
-                candidate = token_urlsafe(nbytes)
-                if candidate[0] not in "-_" and candidate[-1] not in "-_":
-                    return candidate
-
-        self.api_key = pick()
-        self.api_key_readonly = pick()
-        self.ping_key = pick(16)
-        self.save()
-
     def invite_suggestions(self):
         q = User.objects.filter(memberships__project__owner_id=self.owner_id)
         q = q.exclude(memberships__project=self)
diff --git a/hc/accounts/tests/test_project.py b/hc/accounts/tests/test_project.py
index aaeed31c..3445d4fd 100644
--- a/hc/accounts/tests/test_project.py
+++ b/hc/accounts/tests/test_project.py
@@ -23,58 +23,69 @@ class ProjectTestCase(BaseTestCase):
         r = self.client.get(self.url)
         self.assertContains(r, "Change Project Name")
 
-    def test_it_shows_api_keys(self):
+    def test_it_masks_keys_by_default(self):
         self.project.api_key_readonly = "R" * 32
+        self.project.ping_key = "P" * 22
         self.project.save()
 
         self.client.login(username="alice@example.org", password="password")
 
-        form = {"show_api_keys": "1"}
+        r = self.client.get(self.url)
+        self.assertEqual(r.status_code, 200)
+
+        self.assertNotContains(r, "X" * 32)
+        self.assertNotContains(r, "R" * 32)
+        self.assertNotContains(r, "P" * 22)
+
+    def test_it_shows_keys(self):
+        self.project.api_key_readonly = "R" * 32
+        self.project.ping_key = "P" * 22
+        self.project.save()
+
+        self.client.login(username="alice@example.org", password="password")
+
+        form = {"show_keys": "1"}
         r = self.client.post(self.url, form)
         self.assertEqual(r.status_code, 200)
 
         self.assertContains(r, "X" * 32)
         self.assertContains(r, "R" * 32)
+        self.assertContains(r, "P" * 22)
         self.assertContains(r, "Prometheus metrics endpoint")
 
-    def test_it_creates_api_key(self):
+    def test_it_creates_readonly_key(self):
         self.client.login(username="alice@example.org", password="password")
 
-        form = {"create_api_keys": "1"}
+        form = {"create_key": "api_key_readonly"}
         r = self.client.post(self.url, form)
         self.assertEqual(r.status_code, 200)
 
         self.project.refresh_from_db()
-        api_key = self.project.api_key
-        self.assertTrue(len(api_key) > 10)
-        self.assertFalse("b'" in api_key)
+        self.assertEqual(len(self.project.api_key_readonly), 32)
+        self.assertFalse("b'" in self.project.api_key_readonly)
 
-    def test_it_requires_rw_access_to_create_api_key(self):
+    def test_it_requires_rw_access_to_create_key(self):
         self.bobs_membership.role = "r"
         self.bobs_membership.save()
 
         self.client.login(username="bob@example.org", password="password")
-        r = self.client.post(self.url, {"create_api_keys": "1"})
+        r = self.client.post(self.url, {"create_key": "api_key_readonly"})
         self.assertEqual(r.status_code, 403)
 
     def test_it_revokes_api_key(self):
-        self.project.api_key_readonly = "R" * 32
-        self.project.save()
-
         self.client.login(username="alice@example.org", password="password")
-        r = self.client.post(self.url, {"revoke_api_keys": "1"})
+        r = self.client.post(self.url, {"revoke_key": "api_key"})
         self.assertEqual(r.status_code, 200)
 
         self.project.refresh_from_db()
         self.assertEqual(self.project.api_key, "")
-        self.assertEqual(self.project.api_key_readonly, "")
 
     def test_it_requires_rw_access_to_revoke_api_key(self):
         self.bobs_membership.role = "r"
         self.bobs_membership.save()
 
         self.client.login(username="bob@example.org", password="password")
-        r = self.client.post(self.url, {"revoke_api_keys": "1"})
+        r = self.client.post(self.url, {"revoke_key": "api_key"})
         self.assertEqual(r.status_code, 403)
 
     def test_it_adds_team_member(self):
@@ -348,5 +359,5 @@ class ProjectTestCase(BaseTestCase):
         self.bobs_membership.save()
 
         self.client.login(username="bob@example.org", password="password")
-        r = self.client.post(self.url, {"show_api_keys": "1"})
+        r = self.client.post(self.url, {"show_keys": "1"})
         self.assertEqual(r.status_code, 403)
diff --git a/hc/accounts/views.py b/hc/accounts/views.py
index c094f7b9..35f1b493 100644
--- a/hc/accounts/views.py
+++ b/hc/accounts/views.py
@@ -1,6 +1,6 @@
 import base64
 from datetime import timedelta as td
-from secrets import token_bytes
+from secrets import token_bytes, token_urlsafe
 from urllib.parse import urlparse
 import time
 import uuid
@@ -139,6 +139,13 @@ def _check_2fa(request, user):
     return _redirect_after_login(request)
 
 
+def _new_key(nbytes=24):
+    while True:
+        candidate = token_urlsafe(nbytes)
+        if candidate[0] not in "-_" and candidate[-1] not in "-_":
+            return candidate
+
+
 def login(request):
     form = forms.PasswordLoginForm()
     magic_form = forms.EmailLoginForm()
@@ -320,32 +327,40 @@ def project(request, code):
     }
 
     if request.method == "POST":
-        if "create_api_keys" in request.POST:
+        if "create_key" in request.POST:
             if not rw:
                 return HttpResponseForbidden()
 
-            project.set_api_keys()
+            if request.POST["create_key"] == "api_key":
+                project.api_key = _new_key(24)
+            elif request.POST["create_key"] == "api_key_readonly":
+                project.api_key_readonly = _new_key(24)
+            elif request.POST["create_key"] == "ping_key":
+                project.ping_key = _new_key(16)
             project.save()
 
-            ctx["show_api_keys"] = True
-            ctx["api_keys_created"] = True
+            ctx["key_created"] = True
             ctx["api_status"] = "success"
-        elif "revoke_api_keys" in request.POST:
+            ctx["show_keys"] = True
+        elif "revoke_key" in request.POST:
             if not rw:
                 return HttpResponseForbidden()
 
-            project.api_key = ""
-            project.api_key_readonly = ""
-            project.ping_key = None
+            if request.POST["revoke_key"] == "api_key":
+                project.api_key = ""
+            elif request.POST["revoke_key"] == "api_key_readonly":
+                project.api_key_readonly = ""
+            elif request.POST["revoke_key"] == "ping_key":
+                project.ping_key = None
             project.save()
 
-            ctx["api_keys_revoked"] = True
+            ctx["key_revoked"] = True
             ctx["api_status"] = "info"
-        elif "show_api_keys" in request.POST:
+        elif "show_keys" in request.POST:
             if not rw:
                 return HttpResponseForbidden()
 
-            ctx["show_api_keys"] = True
+            ctx["show_keys"] = True
         elif "invite_team_member" in request.POST:
             if not is_manager:
                 return HttpResponseForbidden()
diff --git a/hc/api/urls.py b/hc/api/urls.py
index 13fb9c66..71d1ce00 100644
--- a/hc/api/urls.py
+++ b/hc/api/urls.py
@@ -28,7 +28,7 @@ register_converter(QuoteConverter, "quoted")
 register_converter(SHA1Converter, "sha1")
 
 uuid_urls = [
-    path("", views.ping, name="hc-ping"),
+    path("", views.ping),
     path("fail", views.ping, {"action": "fail"}),
     path("start", views.ping, {"action": "start"}),
     path("<int:exitstatus>", views.ping),
diff --git a/hc/front/templatetags/hc_extras.py b/hc/front/templatetags/hc_extras.py
index 2d7b7d53..18220ef4 100644
--- a/hc/front/templatetags/hc_extras.py
+++ b/hc/front/templatetags/hc_extras.py
@@ -227,3 +227,8 @@ def format_ping_endpoint(ping_url):
     assert ping_url.startswith(settings.PING_ENDPOINT)
     tail = ping_url[len(settings.PING_ENDPOINT) :]
     return mark_safe(FORMATTED_PING_ENDPOINT + escape(tail))
+
+
+@register.filter
+def mask_key(key):
+    return key[:4] + "*" * len(key[4:])
diff --git a/static/css/project.css b/static/css/project.css
new file mode 100644
index 00000000..cf83e57e
--- /dev/null
+++ b/static/css/project.css
@@ -0,0 +1,4 @@
+#api-keys .not-set {
+    color: var(--text-muted);
+    font-style: italic;
+}
\ No newline at end of file
diff --git a/static/js/project.js b/static/js/project.js
index 45c865e4..7a765a94 100644
--- a/static/js/project.js
+++ b/static/js/project.js
@@ -30,4 +30,16 @@ $(function() {
         $("#transfer-confirm").prop("disabled", !this.value);
     });
 
+    $("a[data-revoke-key]").click(function() {
+        $("#revoke-key-type").val(this.dataset.revokeKey);
+        $("#revoke-key-modal .name").text(this.dataset.name);
+        $("#revoke-key-modal").modal("show");
+    })
+
+    $("a[data-create-key]").click(function() {
+        $("#create-key-type").val(this.dataset.createKey);
+        $("#create-key-form").submit();
+    })
+
+
 });
diff --git a/templates/accounts/project.html b/templates/accounts/project.html
index 7ca8f6e7..26975531 100644
--- a/templates/accounts/project.html
+++ b/templates/accounts/project.html
@@ -75,86 +75,124 @@
             {% endif %}
         </div>
 
+        {% if rw %}
         <div class="panel panel-{{ api_status|default:'default' }}">
             <div class="panel-body settings-block">
                 <h2>API Access</h2>
-                {% if project.api_key %}
-                    {% if show_api_keys %}
-                        <p>
-                            API key: <br />
-                            <pre>{{ project.api_key }}</pre>
-                        </p>
-                        {% if project.api_key_readonly %}
-                        <p>
-                            API key (read-only): <br />
-                            <pre>{{ project.api_key_readonly }}</pre>
-                        </p>
-                        {% endif %}
-                        {% if project.ping_key %}
-                        <p>
-                            Ping key: <br />
-                            <pre>{{ project.ping_key }}</pre>
-                        </p>
-                        {% endif %}
-                        <p>See also:</p>
-                        <ul>
-                            <li><a href="{% url 'hc-serve-doc' 'api' %}">API documentation</a></li>
+                <table id="api-keys" class="table">
+                    <tr>
+                        <td>API key</td>
+                        <td>
+                            {% if project.api_key %}
+                            {% if show_keys %}
+                                <code>{{ project.api_key }}</code>
+                            {% else %}
+                                <code>{{ project.api_key|mask_key }}</code>
+                            {% endif %}
+                            {% else %}
+                            <span class="not-set">not set</span>
+                            {% endif %}
+                        </td>
+                        <td class="text-right">
+                            {% if project.api_key %}
+                            <a href="#"
+                               data-revoke-key="api_key"
+                               data-name="API key">Revoke</a>
+                            {% else %}
+                            <a href="#" data-create-key="api_key">Create</a>
+                            {% endif %}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>API key (read-only)</td>
+                        <td>
                             {% if project.api_key_readonly %}
-                            {% if enable_prometheus %}
-                            <li>
-                                <a href="{% url 'hc-metrics' project.code project.api_key_readonly %}">Prometheus metrics endpoint</a>
-                            </li>
+                            {% if show_keys %}
+                                <code>{{ project.api_key_readonly }}</code>
+                            {% else %}
+                                <code>{{ project.api_key_readonly|mask_key }}</code>
                             {% endif %}
-                            <li>
-                                <a href="{{ project.dashboard_url }}">Read-only dashboard</a>
-                                (<a href="https://github.com/healthchecks/dashboard/#security">security considerations</a>)
-                            </li>
+                            {% else %}
+                            <span class="not-set">not set</span>
                             {% endif %}
-                        </ul>
-                        <button
-                            data-toggle="modal"
-                            data-target="#revoke-api-key-modal"
-                            class="btn btn-danger pull-right">Revoke</button>
+                        </td>
+                        <td class="text-right">
+                            {% if project.api_key_readonly %}
+                            <a href="#"
+                               data-revoke-key="api_key_readonly"
+                               data-name="read-only API key">Revoke</a>
+                            {% else %}
+                            <a href="#" data-create-key="api_key_readonly">Create</a>
+                            {% endif %}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>Ping key</td>
+                        <td>
+                            {% if project.ping_key %}
+                            {% if show_keys %}
+                                <code>{{ project.ping_key }}</code>
+                            {% else %}
+                                <code>{{ project.ping_key|mask_key }}</code>
+                            {% endif %}
+                            {% else %}
+                            <span class="not-set">not set</span>
+                            {% endif %}
+                        </td>
+                        <td class="text-right">
+                            {% if project.ping_key %}
+                            <a href="#"
+                               data-revoke-key="ping_key"
+                               data-name="Ping key">Revoke</a>
+                            {% else %}
+                            <a href="#" data-create-key="ping_key">Create</a>
+                            {% endif %}
+                        </td>
+                    </tr>
+                </table>
 
-                    {% else %}
-                        <form method="post">
-                        <span class="ic-ok"></span>
-                        API access is enabled.
-                            {% csrf_token %}
-
-                            {% if rw %}
-                            <button
-                                type="submit"
-                                name="show_api_keys"
-                                class="btn btn-default pull-right">Show API Keys</button>
-                            {% endif %}
-                        </form>
+                <p>See also:</p>
+                <ul>
+                    <li><a href="{% url 'hc-serve-doc' 'api' %}">API documentation</a></li>
+                    {% if project.api_key_readonly and show_keys %}
+                    {% if enable_prometheus %}
+                    <li>
+                        <a href="{% url 'hc-metrics' project.code project.api_key_readonly %}">Prometheus metrics endpoint</a>
+                    </li>
                     {% endif %}
-                {% else %}
-                    <span class="ic-cancel"></span>
-                    API access is disabled.
-                    <form method="post">
-                        {% csrf_token %}
-                        <button
-                            type="submit"
-                            name="create_api_keys"
-                            class="btn btn-default pull-right">Create API Keys</button>
-                    </form>
+                    <li>
+                        <a href="{{ project.dashboard_url }}">Read-only dashboard</a>
+                        (<a href="https://github.com/healthchecks/dashboard/#security">security considerations</a>)
+                    </li>
+                    {% endif %}
+                </ul>
+
+                {% if not show_keys %}
+                {% if project.api_key or project.api_key_readonly or project.ping_key %}
+                <form method="post">
+                    {% csrf_token %}
+                    <button
+                        type="submit"
+                        name="show_keys"
+                        class="btn btn-default pull-right">Show Keys</button>
+                </form>
+                {% endif %}
                 {% endif %}
             </div>
 
-            {% if api_keys_created %}
+            {% if key_created %}
             <div class="panel-footer">
-                API keys created
+                Key created
             </div>
             {% endif %}
 
-           {% if api_keys_revoked %}
+           {% if key_revoked %}
             <div class="panel-footer">
-                API keys revoked
+                Key revoked
             </div>
             {% endif %}
         </div>
+        {% endif %}
 
         {% with invite_suggestions=project.invite_suggestions %}
         <div class="panel panel-{{ team_status|default:'default' }}">
@@ -320,29 +358,37 @@
     </div>
 </div>
 
-<div id="revoke-api-key-modal" class="modal">
+<form id="create-key-form" method="post">
+    {% csrf_token %}
+    <input id="create-key-type" type="hidden" name="create_key">
+</form>
+
+<div id="revoke-key-modal" class="modal">
     <div class="modal-dialog">
-        <form id="revoke-api-key-form" method="post">
+        <form id="revoke-key-form" method="post">
             {% csrf_token %}
+            <input id="revoke-key-type" type="hidden" name="revoke_key">
             <div class="modal-content">
                 <div class="modal-header">
                     <button type="button" class="close" data-dismiss="modal">&times;</button>
-                    <h4>Revoke API Keys?</h4>
+                    <h4>Revoke <span class="name"></span>?</h4>
                 </div>
                 <div class="modal-body">
-                    <p>You are about to revoke your current API keys.</p>
-                    <p>Afterwards, you can create new API keys, but there will
-                        be <strong>no way of getting the current API
-                        keys back</strong>.
+                    <p>
+                        You are about to revoke your current
+                        <span class="name"></span>.
+                    </p>
+                    <p>
+                        Afterwards, you can generate a new key, but there will
+                        be <strong>no way of getting the key's current value back</strong>.
                     </p>
                     <p>Are you sure?</p>
                 </div>
                 <div class="modal-footer">
                     <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
-                    <button
-                        type="submit"
-                        name="revoke_api_keys"
-                        class="btn btn-danger">Revoke API Keys</button>
+                    <button type="submit" class="btn btn-danger">
+                        Revoke <span class="name"></span>
+                    </button>
                 </div>
             </div>
         </form>
diff --git a/templates/base.html b/templates/base.html
index 3300a300..bc46f82a 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -58,6 +58,7 @@
     <link rel="stylesheet" href="{% static 'css/syntax.css' %}" type="text/css">
     <link rel="stylesheet" href="{% static 'css/welcome.css' %}" type="text/css">
     <link rel="stylesheet" href="{% static 'css/set_password.css' %}" type="text/css">
+    <link rel="stylesheet" href="{% static 'css/project.css' %}" type="text/css">
     {% endcompress %}
 </head>
 <body class="page-{{ page }}{% if request.user.is_authenticated and request.profile.theme == 'dark' %} dark{% endif%}">