diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c
index b38207cbf..286586d8c 100644
--- a/plugins/fetchinvoice.c
+++ b/plugins/fetchinvoice.c
@@ -809,12 +809,9 @@ static const struct plugin_command commands[] = { {
 static void init(struct plugin *p, const char *buf UNUSED,
 		 const jsmntok_t *config UNUSED)
 {
-	const char *field;
-
-	field = rpc_delve(tmpctx, p, "getinfo",
-			  take(json_out_obj(NULL, NULL, NULL)), ".id");
-	if (!node_id_from_hexstr(field, strlen(field), &local_id))
-		plugin_err(p, "getinfo didn't contain valid id: '%s'", field);
+	rpc_scan(p, "getinfo",
+		 take(json_out_obj(NULL, NULL, NULL)),
+		 "{id:%}", JSON_SCAN(json_to_node_id, &local_id));
 }
 
 static const struct plugin_hook hooks[] = {
diff --git a/plugins/keysend.c b/plugins/keysend.c
index b95f93f24..eb1e0716d 100644
--- a/plugins/keysend.c
+++ b/plugins/keysend.c
@@ -95,18 +95,13 @@ REGISTER_PAYMENT_MODIFIER(keysend, struct keysend_data *, keysend_init,
 static void init(struct plugin *p, const char *buf UNUSED,
 		 const jsmntok_t *config UNUSED)
 {
-	const char *field;
-
-	field = rpc_delve(tmpctx, p, "getinfo",
-			  take(json_out_obj(NULL, NULL, NULL)), ".id");
-	if (!node_id_from_hexstr(field, strlen(field), &my_id))
-		plugin_err(p, "getinfo didn't contain valid id: '%s'", field);
-
-	field =
-	    rpc_delve(tmpctx, p, "listconfigs",
-		      take(json_out_obj(NULL, "config", "max-locktime-blocks")),
-		      ".max-locktime-blocks");
-	maxdelay_default = atoi(field);
+	rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)),
+		 "{id:%}", JSON_SCAN(json_to_node_id, &my_id));
+
+	rpc_scan(p, "listconfigs",
+		 take(json_out_obj(NULL, "config", "max-locktime-blocks")),
+		 "{max-locktime-blocks:%}",
+		 JSON_SCAN(json_to_number, &maxdelay_default));
 }
 
 struct payment_modifier *pay_mods[8] = {
diff --git a/plugins/libplugin.c b/plugins/libplugin.c
index 814e088b1..2e27aa5b4 100644
--- a/plugins/libplugin.c
+++ b/plugins/libplugin.c
@@ -490,18 +490,19 @@ static struct json_out *start_json_request(const tal_t *ctx,
 	return jout;
 }
 
-/* Synchronous routine to send command and extract single field from response */
-const char *rpc_delve(const tal_t *ctx,
-		      struct plugin *plugin,
-		      const char *method,
-		      const struct json_out *params TAKES,
-		      const char *guide)
+/* Synchronous routine to send command and extract fields from response */
+void rpc_scan(struct plugin *plugin,
+	      const char *method,
+	      const struct json_out *params TAKES,
+	      const char *guide,
+	      ...)
 {
 	bool error;
-	const jsmntok_t *contents, *t;
+	const jsmntok_t *contents;
 	int reqlen;
-	const char *ret;
+	const char *p;
 	struct json_out *jout;
+	va_list ap;
 
 	jout = start_json_request(tmpctx, 0, method, params);
 	finish_and_send_json(plugin->rpc_conn->fd, jout);
@@ -511,14 +512,15 @@ const char *rpc_delve(const tal_t *ctx,
 		plugin_err(plugin, "Got error reply to %s: '%.*s'",
 		     method, reqlen, membuf_elems(&plugin->rpc_conn->mb));
 
-	t = json_delve(membuf_elems(&plugin->rpc_conn->mb), contents, guide);
-	if (!t)
-		plugin_err(plugin, "Could not find %s in reply to %s: '%.*s'",
-			   guide, method, reqlen, membuf_elems(&plugin->rpc_conn->mb));
+	p = membuf_consume(&plugin->rpc_conn->mb, reqlen);
 
-	ret = json_strdup(ctx, membuf_elems(&plugin->rpc_conn->mb), t);
-	membuf_consume(&plugin->rpc_conn->mb, reqlen);
-	return ret;
+	va_start(ap, guide);
+	error = !json_scanv(p, contents, guide, ap);
+	va_end(ap);
+
+	if (error)
+		plugin_err(plugin, "Could not parse %s in reply to %s: '%.*s'",
+			   guide, method, reqlen, membuf_elems(&plugin->rpc_conn->mb));
 }
 
 static void handle_rpc_reply(struct plugin *plugin, const jsmntok_t *toks)
diff --git a/plugins/libplugin.h b/plugins/libplugin.h
index d9b3fd540..1f62b3deb 100644
--- a/plugins/libplugin.h
+++ b/plugins/libplugin.h
@@ -190,13 +190,13 @@ command_success_str(struct command *cmd, const char *str);
 struct command_result *WARN_UNUSED_RESULT
 command_hook_success(struct command *cmd);
 
-/* Synchronous helper to send command and extract single field from
+/* Synchronous helper to send command and extract fields from
  * response; can only be used in init callback. */
-const char *rpc_delve(const tal_t *ctx,
-		      struct plugin *plugin,
-		      const char *method,
-		      const struct json_out *params TAKES,
-		      const char *guide);
+void rpc_scan(struct plugin *plugin,
+	      const char *method,
+	      const struct json_out *params TAKES,
+	      const char *guide,
+	      ...);
 
 /* Send an async rpc request to lightningd. */
 struct command_result *
diff --git a/plugins/offers.c b/plugins/offers.c
index 766d83f84..e31d1d9a1 100644
--- a/plugins/offers.c
+++ b/plugins/offers.c
@@ -42,24 +42,18 @@ static void init(struct plugin *p,
 		 const char *buf UNUSED,
 		 const jsmntok_t *config UNUSED)
 {
-	const char *field;
 	struct pubkey k;
 
-	field =
-	    rpc_delve(tmpctx, p, "getinfo",
-		      take(json_out_obj(NULL, NULL, NULL)),
-		      ".id");
-	if (!pubkey_from_hexstr(field, strlen(field), &k))
-		abort();
+	rpc_scan(p, "getinfo",
+		 take(json_out_obj(NULL, NULL, NULL)),
+		 "{id:%}", JSON_SCAN(json_to_pubkey, &k));
 	if (secp256k1_xonly_pubkey_from_pubkey(secp256k1_ctx, &id.pubkey,
 					       NULL, &k.pubkey) != 1)
 		abort();
 
-	field =
-	    rpc_delve(tmpctx, p, "listconfigs",
-		      take(json_out_obj(NULL, "config", "cltv-final")),
-		      ".cltv-final");
-	cltv_final = atoi(field);
+	rpc_scan(p, "listconfigs",
+		 take(json_out_obj(NULL, "config", "cltv-final")),
+		 "{cltv-final:%}", JSON_SCAN(json_to_number, &cltv_final));
 }
 
 static const struct plugin_command commands[] = {
diff --git a/plugins/pay.c b/plugins/pay.c
index 363f77703..9f0324a70 100644
--- a/plugins/pay.c
+++ b/plugins/pay.c
@@ -1890,18 +1890,13 @@ static struct command_result *json_listpays(struct command *cmd,
 static void init(struct plugin *p,
 		  const char *buf UNUSED, const jsmntok_t *config UNUSED)
 {
-	const char *field;
-
-	field = rpc_delve(tmpctx, p, "getinfo",
-			  take(json_out_obj(NULL, NULL, NULL)), ".id");
-	if (!node_id_from_hexstr(field, strlen(field), &my_id))
-		plugin_err(p, "getinfo didn't contain valid id: '%s'", field);
-
-	field = rpc_delve(tmpctx, p, "listconfigs",
-			  take(json_out_obj(NULL,
-					    "config", "max-locktime-blocks")),
-			  ".max-locktime-blocks");
-	maxdelay_default = atoi(field);
+	rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)),
+		 "{id:%}", JSON_SCAN(json_to_node_id, &my_id));
+
+	rpc_scan(p, "listconfigs",
+		 take(json_out_obj(NULL, "config", "max-locktime-blocks")),
+		 "{max-locktime-blocks:%}",
+		 JSON_SCAN(json_to_number, &maxdelay_default));
 }
 
 struct payment_modifier *paymod_mods[] = {