diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss
index 9d58c999c3..500c46b5fd 100644
--- a/res/css/views/dialogs/_DevtoolsDialog.scss
+++ b/res/css/views/dialogs/_DevtoolsDialog.scss
@@ -189,3 +189,37 @@ limitations under the License.
         }
     }
 }
+
+.mx_DevTools_VerificationRequest {
+    border: 1px solid #cccccc;
+    border-radius: 3px;
+    padding: 1px 5px;
+    margin-bottom: 6px;
+    font-family: $monospace-font-family;
+
+    dl {
+        display: grid;
+        grid-template-columns: max-content auto;
+        margin: 0;
+    }
+
+    dd {
+        grid-column-start: 2;
+    }
+
+    dd:empty {
+        color: #666666;
+        &::after {
+            content: "(empty)";
+        }
+    }
+
+    dt {
+        font-weight: bold;
+        grid-column-start: 1;
+    }
+
+    dt::after {
+        content: ":";
+    }
+}
diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 34b2f5a52b..348965582b 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React from 'react';
+import React, {useState, useEffect} from 'react';
 import PropTypes from 'prop-types';
 import * as sdk from '../../../index';
 import SyntaxHighlight from '../elements/SyntaxHighlight';
@@ -22,6 +22,16 @@ import { _t } from '../../../languageHandler';
 import { Room } from "matrix-js-sdk";
 import Field from "../elements/Field";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import {useEventEmitter} from "../../../hooks/useEventEmitter";
+
+import {
+    PHASE_UNSENT,
+    PHASE_REQUESTED,
+    PHASE_READY,
+    PHASE_DONE,
+    PHASE_STARTED,
+    PHASE_CANCELLED,
+} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
 
 class GenericEditor extends React.PureComponent {
     // static propTypes = {onBack: PropTypes.func.isRequired};
@@ -605,12 +615,97 @@ class ServersInRoomList extends React.PureComponent {
     }
 }
 
+const PHASE_MAP = {
+    [PHASE_UNSENT]: "unsent",
+    [PHASE_REQUESTED]: "requested",
+    [PHASE_READY]: "ready",
+    [PHASE_DONE]: "done",
+    [PHASE_STARTED]: "started",
+    [PHASE_CANCELLED]: "cancelled",
+};
+
+function VerificationRequest({txnId, request}) {
+    const [, updateState] = useState();
+    const [timeout, setRequestTimeout] = useState(request.timeout);
+
+    /* Re-render if something changes state */
+    useEventEmitter(request, "change", updateState);
+
+    /* Keep re-rendering if there's a timeout */
+    useEffect(() => {
+        if (request.timeout == 0) return;
+
+        /* Note that request.timeout is a getter, so its value changes */
+        const id = setInterval(() => {
+           setRequestTimeout(request.timeout);
+        }, 500);
+
+        return () => { clearInterval(id); };
+    }, [request]);
+
+    return (<div className="mx_DevTools_VerificationRequest">
+        <dl>
+            <dt>Transaction</dt>
+            <dd>{txnId}</dd>
+            <dt>Phase</dt>
+            <dd>{PHASE_MAP[request.phase] || request.phase}</dd>
+            <dt>Timeout</dt>
+            <dd>{Math.floor(timeout / 1000)}</dd>
+            <dt>Methods</dt>
+            <dd>{request.methods && request.methods.join(", ")}</dd>
+            <dt>requestingUserId</dt>
+            <dd>{request.requestingUserId}</dd>
+            <dt>observeOnly</dt>
+            <dd>{JSON.stringify(request.observeOnly)}</dd>
+        </dl>
+    </div>);
+}
+
+class VerificationExplorer extends React.Component {
+    static getLabel() {
+        return _t("Verification Requests");
+    }
+
+    /* Ensure this.context is the cli */
+    static contextType = MatrixClientContext;
+
+    onNewRequest = () => {
+        this.forceUpdate();
+    }
+
+    componentDidMount() {
+        const cli = this.context;
+        cli.on("crypto.verification.request", this.onNewRequest);
+    }
+
+    componentWillUnmount() {
+        const cli = this.context;
+        cli.off("crypto.verification.request", this.onNewRequest);
+    }
+
+    render() {
+        const cli = this.context;
+        const room = this.props.room;
+        const inRoomChannel = cli._crypto._inRoomVerificationRequests;
+        const inRoomRequests = (inRoomChannel._requestsByRoomId || new Map()).get(room.roomId) || new Map();
+
+        return (<div>
+            <div className="mx_Dialog_content">
+                {Array.from(inRoomRequests.entries()).reverse().map(([txnId, request]) =>
+                    <VerificationRequest txnId={txnId} request={request} key={txnId} />,
+                )}
+            </div>
+        </div>);
+    }
+}
+
 const Entries = [
     SendCustomEvent,
     RoomStateExplorer,
     SendAccountData,
     AccountDataExplorer,
     ServersInRoomList,
+    VerificationExplorer,
 ];
 
 export default class DevtoolsDialog extends React.PureComponent {
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index b55b28ce97..82eb660d9d 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1524,6 +1524,7 @@
     "Explore Room State": "Explore Room State",
     "Explore Account Data": "Explore Account Data",
     "View Servers in Room": "View Servers in Room",
+    "Verification Requests": "Verification Requests",
     "Toolbox": "Toolbox",
     "Developer Tools": "Developer Tools",
     "An error has occurred.": "An error has occurred.",