diff --git a/lang/cpp/tests/Makefile.am b/lang/cpp/tests/Makefile.am
index 0b275955..9e67dff3 100644
--- a/lang/cpp/tests/Makefile.am
+++ b/lang/cpp/tests/Makefile.am
@@ -1,36 +1,45 @@
 # Makefile.am - Makefile for GPGME Cpp tests.
 # Copyright (C) 2018 Intevation GmbH
 #
 # This file is part of GPGME.
 #
 # GPGME is free software; you can redistribute it and/or modify it
 # under the terms of the GNU Lesser General Public License as
 # published by the Free Software Foundation; either version 2.1 of the
 # License, or (at your option) any later version.
 #
 # GPGME is distributed in the hope that it will be useful, but WITHOUT
 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
 # Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <https://www.gnu.org/licenses/>.
 
 ## Process this file with automake to produce Makefile.in
 
 AM_LDFLAGS = -no-install
 
 LDADD = ../../cpp/src/libgpgmepp.la \
         ../../../src/libgpgme.la @GPG_ERROR_LIBS@ \
         @LDADD_FOR_TESTS_KLUDGE@ -lstdc++
 
 AM_CPPFLAGS = -I$(top_srcdir)/lang/cpp/src -I$(top_builddir)/src \
               @GPG_ERROR_CFLAGS@ @GPG_ERROR_CFLAGS@ \
               @LIBASSUAN_CFLAGS@ -DBUILDING_GPGMEPP \
               -DTOP_SRCDIR="$(top_srcdir)"
 
 run_getkey_SOURCES = run-getkey.cpp
 run_keylist_SOURCES = run-keylist.cpp
 run_verify_SOURCES = run-verify.cpp
+if !HAVE_W32_SYSTEM
+run_wkdlookup_SOURCES = run-wkdlookup.cpp
+endif
 
-noinst_PROGRAMS = run-getkey run-keylist run-verify
+if HAVE_W32_SYSTEM
+programs_unix =
+else
+programs_unix = run-wkdlookup
+endif
+
+noinst_PROGRAMS = run-getkey run-keylist run-verify $(programs_unix)
diff --git a/lang/cpp/tests/run-wkdlookup.cpp b/lang/cpp/tests/run-wkdlookup.cpp
new file mode 100644
index 00000000..71c4e704
--- /dev/null
+++ b/lang/cpp/tests/run-wkdlookup.cpp
@@ -0,0 +1,156 @@
+/*
+    run-wkdlookup.cpp
+
+    This file is part of GpgMEpp's test suite.
+    Copyright (c) 2021 g10 Code GmbH
+    Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
+
+    GPGME++ is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    GPGME++ is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with GPGME++; see the file COPYING.LIB.  If not, write to the
+    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include "context.h"
+#include "data.h"
+#include "defaultassuantransaction.h"
+#include "key.h"
+
+#include <memory>
+#include <sstream>
+#include <iostream>
+#include <thread>
+
+using namespace GpgME;
+
+static int
+show_usage (int ex)
+{
+  fputs ("usage: run-wkdlookup <email address>\n\n"
+         , stderr);
+  exit (ex);
+}
+
+int
+main (int argc, char **argv)
+{
+    int last_argc = -1;
+
+    if (argc) {
+        argc--; argv++;
+    }
+
+    while (argc && last_argc != argc ) {
+        last_argc = argc;
+        if (!strcmp (*argv, "--")) {
+            argc--; argv++;
+            break;
+        } else if (!strcmp (*argv, "--help")) {
+            show_usage (0);
+        } else if (!strncmp (*argv, "--", 2)) {
+            show_usage (1);
+        }
+    }
+
+    if (argc != 1) {
+        show_usage (1);
+    }
+
+    const std::string email{*argv};
+
+    GpgME::initializeLibrary();
+    Error err;
+    auto ctx = std::unique_ptr<Context>{Context::createForEngine(AssuanEngine, &err)};
+    if (!ctx) {
+        std::cerr << "Failed to get context (Error: " << err.asString() << ")\n";
+        return -1;
+    }
+
+    const std::string dirmngrSocket = GpgME::dirInfo("dirmngr-socket");
+    if ((err = ctx->setEngineFileName(dirmngrSocket.c_str()))) {
+        std::cerr << "Failed to set engine file name (Error: " << err.asString() << ")\n";
+        return -1;
+    }
+    if ((err = ctx->setEngineHomeDirectory(""))) {
+        std::cerr << "Failed to set engine home directory (Error: " << err.asString() << ")\n";
+        return -1;
+    }
+
+    // try do connect to dirmngr
+    err = ctx->assuanTransact("GETINFO version");
+    if (err && err.code() != GPG_ERR_ASS_CONNECT_FAILED) {
+        std::cerr << "Failed to start assuan transaction (Error: " << err.asString() << ")\n";
+        return -1;
+    }
+    if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) {
+        std::cerr << "Starting dirmngr ...\n";
+        auto spawnCtx = std::unique_ptr<Context>{Context::createForEngine(SpawnEngine, &err)};
+        if (!spawnCtx) {
+            std::cerr << "Failed to get context for spawn engine (Error: " << err.asString() << ")\n";
+            return -1;
+        }
+
+        const auto dirmngrProgram = GpgME::dirInfo("dirmngr-name");
+        const auto homedir = GpgME::dirInfo("homedir");
+        const char *argv[] = {
+            dirmngrProgram,
+            "--homedir",
+            homedir,
+            "--daemon",
+            NULL
+        };
+        auto ignoreIO = Data{Data::null};
+        err = spawnCtx->spawnAsync(dirmngrProgram, argv,
+                                   ignoreIO, ignoreIO, ignoreIO,
+                                   Context::SpawnDetached);
+        if (err) {
+            std::cerr << "Failed to start dirmngr (Error: " << err.asString() << ")\n";
+            return -1;
+        }
+
+        // wait for socket to become available
+        int cnt = 0;
+        do {
+            ++cnt;
+            std::cerr << "Waiting for dirmngr to start ...\n";
+            std::this_thread::sleep_for(std::chrono::milliseconds{250 * cnt});
+            err = ctx->assuanTransact("GETINFO version");
+        } while (err.code() == GPG_ERR_ASS_CONNECT_FAILED && cnt < 5);
+    }
+
+    const auto cmd = std::string{"WKD_GET "} + email;
+    err = ctx->assuanTransact(cmd.c_str());
+    if (err && err.code() != GPG_ERR_NO_NAME && err.code() != GPG_ERR_NO_DATA) {
+        std::cerr << "Error: WKD_GET returned " << err.asString() << "\n";
+        return -1;
+    }
+
+    const auto transaction = std::unique_ptr<DefaultAssuanTransaction>(dynamic_cast<DefaultAssuanTransaction*>(ctx->takeLastAssuanTransaction().release()));
+    const auto source = transaction->firstStatusLine("SOURCE");
+    const auto rawData = transaction->data();
+    if (rawData.size() == 0) {
+        std::cout << "No key found for " << email << "\n";
+    } else {
+        const auto data = GpgME::Data{rawData.c_str(), rawData.size()};
+        const auto keys = data.toKeys(GpgME::OpenPGP);
+        for (const auto &key : keys) {
+            std::cout << "Found key for " << email << " at " << source << ":\n" << key << "\n";
+        }
+    }
+
+    return 0;
+}