How to implement DataForSEO from scratch in Wappler Node.js?

Hi everyone,

I want to use the DataForSEO API in my Wappler Node.js project to track Google rankings.

I want to use their standard tier to keep costs low. The catch is that this tier takes about 5 minutes to process a request and generate the results.

I have absolutely no idea how to handle a 5-minute delayed response in Wappler. I assume a normal Server Connect API Action would just time out.

How exactly is this done? Can someone explain the workflow to me from the ground up? How do I set up Wappler to send the request an external server general and then catch the data 5 minutes later?

I would really appreciate a conceptual explanation or examples if anyone has successfully built this!

Thanks!

The best solution is to probably use the BullQueues extension, and then offload the serverconnect to it. The api action lets you set a timeout, so you can set it high. Since you’re putting it in a queue, you won’t need to keep the page open.

In the server serverconnect you’re offloading to Bull you can then insert it into your database. That’s providing there isn’t a way to send the request via one API URL and then request the data via another, and it has to be done in one api request.

Today I realized it's not that difficult after all.

Sending and receiving data works perfectly.

My only problem is interpreting the data for insertion into the database.

I've spent half the day with Gemini just trying to extract the data from $post to write it to the database, and I'm not getting anywhere.

Does anyone have any idea how I should do this?

Here's what I'm getting from Dataforseo.

[
  {
    "version": "0.1.20260327",
    "status_code": 20000,
    "status_message": "Ok.",
    "time": "0.0504 sec.",
    "cost": 0.00105,
    "tasks_count": 1,
    "tasks_error": 0,
    "tasks": [
      {
        "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "status_code": 20000,
        "status_message": "Ok.",
        "time": "0.0248 sec.",
        "cost": 0.00105,
        "result_count": 1,
        "path": [
          "v3",
          "serp",
          "google",
          "organic",
          "task_get",
          "regular",
          "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
        ],
        "data": {
          "api": "serp",
          "function": "task_get",
          "se": "google",
          "se_type": "organic",
          "keyword": "teambuilding duisburg",
          "language_code": "de",
          "location_code": 2276,
          "depth": 20,
          "postback_url": "https://[DEINE-DOMAIN].de/api/[INTERNER-PFAD]?target_id=1",
          "postback_data": "regular",
          "device": "desktop",
          "os": "windows"
        },
        "result": [
          {
            "keyword": "teambuilding duisburg",
            "type": "organic",
            "se_domain": "google.com",
            "location_code": 2276,
            "language_code": "de",
            "check_url": "https://www.google.com/search?q=teambuilding%20duisburg&hl=de&gl=DE&ie=UTF-8&uule=w+CAIQIFISCWu-scIecppHEVvlkY5rXeh1",
            "datetime": "2026-03-28 19:23:30 +00:00",
            "spell": null,
            "item_types": [
              "organic",
              "people_also_ask",
              "images",
              "related_searches",
              "local_pack"
            ],
            "se_results_count": 341000,
            "pages_count": 2,
            "items_count": 10,
            "items": [
              {
                "type": "organic",
                "rank_group": 1,
                "rank_absolute": 1,
                "page": 1,
                "domain": "www.hirschfeld.de",
                "title": "Teamevents in Duisburg - die besten Ideen für Ihr Teambuilding",
                "description": "Beim Teambuilding in Duisburg profitieren Teams von abwechslungsreichen Aktivitäten...",
                "url": "https://www.hirschfeld.de/teambuilding/duisburg/",
                "breadcrumb": "https://www.hirschfeld.de › teambuilding › duisburg"
              },
              {
                "type": "organic",
                "rank_group": 2,
                "rank_absolute": 2,
                "page": 1,
                "domain": "www.younited.de",
                "title": "Teamevents in Duisburg: 37 coole Teambuilding Ideen",
                "description": "Ideen für Teamevents in Duisburg · Team Challenge 'The Game'...",
                "url": "https://www.younited.de/teamevent-duisburg.html",
                "breadcrumb": "https://www.younited.de › Teamevents"
              }
            ]
          }
        ]
      }
    ]
  }
]

A mix of objects and arrays.

Here's my current API (it doesn't work).

{
  "meta": {
    "$_GET": [
      {
        "type": "text",
        "name": "target_id"
      }
    ],
    "$_POST": [
      {
        "type": "array",
        "name": "tasks",
        "sub": [
          {
            "type": "array",
            "name": "result",
            "sub": [
              {
                "type": "array",
                "name": "items",
                "sub": [
                  {
                    "type": "number",
                    "name": "rank_absolute"
                  },
                  {
                    "type": "number",
                    "name": "rank_group"
                  },
                  {
                    "type": "text",
                    "name": "url"
                  },
                  {
                    "type": "text",
                    "name": "domain"
                  },
                  {
                    "type": "text",
                    "name": "type"
                  },
                  {
                    "type": "text",
                    "name": "title"
                  },
                  {
                    "type": "text",
                    "name": "description"
                  }
                ]
              }
            ]
          },
          {
            "type": "text",
            "name": "id"
          }
        ]
      }
    ]
  },
  "exec": {
    "steps": {
      "name": "loop_rankings",
      "module": "core",
      "action": "repeat",
      "options": {
        "repeat": "{{$_POST.tasks[0].result[0].items}}",
        "outputFields": [],
        "exec": {
          "steps": {
            "name": "insert",
            "module": "dbupdater",
            "action": "insert",
            "options": {
              "connection": "db",
              "sql": {
                "type": "insert",
                "values": [
                  {
                    "table": "seo_rank_history",
                    "column": "check_timestamp",
                    "type": "datetime",
                    "value": "{{NOW}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "description",
                    "type": "text",
                    "value": "{{description}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "domain",
                    "type": "text",
                    "value": "{{domain}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "found_url",
                    "type": "text",
                    "value": "{{url}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "rank_absolute",
                    "type": "number",
                    "value": "{{rank_absolute}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "rank_change",
                    "type": "number",
                    "value": "{{query_last_rank.rank_absolute ? (query_last_rank.rank_absolute - rank_absolute) : 0}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "rank_group",
                    "type": "number",
                    "value": "{{rank_group}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "result_type",
                    "type": "text",
                    "value": "{{type}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "target_id",
                    "type": "number",
                    "value": "{{$_GET.target_id}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "task_external_id",
                    "type": "text",
                    "value": "{{$_POST.tasks[0].id}}"
                  },
                  {
                    "table": "seo_rank_history",
                    "column": "title",
                    "type": "text",
                    "value": "{{title}}"
                  }
                ],
                "table": "seo_rank_history",
                "query": "insert into `seo_rank_history` (`check_timestamp`, `description`, `domain`, `found_url`, `rank_absolute`, `rank_change`, `rank_group`, `result_type`, `target_id`, `task_external_id`, `title`) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
              }
            },
            "meta": [
              {
                "name": "identity",
                "type": "text"
              },
              {
                "name": "affected",
                "type": "number"
              }
            ]
          }
        }
      },
      "meta": [
        {
          "name": "$index",
          "type": "number"
        },
        {
          "name": "$number",
          "type": "number"
        },
        {
          "name": "$name",
          "type": "text"
        },
        {
          "name": "$value",
          "type": "object"
        },
        {
          "name": "result",
          "type": "array",
          "sub": [
            {
              "name": "items",
              "type": "array",
              "sub": [
                {
                  "name": "rank_absolute",
                  "type": "number"
                },
                {
                  "name": "rank_group",
                  "type": "number"
                },
                {
                  "name": "url",
                  "type": "text"
                },
                {
                  "name": "domain",
                  "type": "text"
                },
                {
                  "name": "type",
                  "type": "text"
                },
                {
                  "name": "title",
                  "type": "text"
                },
                {
                  "name": "description",
                  "type": "text"
                }
              ]
            }
          ]
        },
        {
          "name": "id",
          "type": "text"
        }
      ],
      "outputType": "array"
    }
  }
}

How are you receiving the info? Is this a webhook? You talk about about an API and then you show a POST method..
If you use beeceptor, or some other tool, where is the data posted?

Hi Franse,

Yes, that's a webhook from dataforseo.com.

I found the solution.

Gemini instructed me to parse the data directly in $_post, which didn't work.

I simply solved it using setvalues, and now it works.

To explain:

I send the request via an API action.

I receive the data in a separate query, "seo_receiver," to avoid the timeout issue.

Dataforseo sends the data directly to the API as soon as it's ready: "/api/admin/seo/ranking/seo_receiver?target_id={{query_get_seo_keyword_targets.id}"