Trouble aggregating nested arrays from webhook JSON in Make (flatten/map on suppliers[].offer.variants[].prices)

Hi everyone,

I’m struggling with nested arrays coming from a webhook and I’m pretty sure I’m overcomplicating it.

Context

I receive a JSON payload via a Custom Webhook It contains:

  • data.request.request_variants[] – the variants (labels are identical across all suppliers)

  • data.suppliers[] – an array of suppliers

  • Each supplier has offer.variants[]

  • Each variant has a prices[] array with ct_per_kwh (price as string) – or an empty array if no price was submitted.

I want to calculate, per variant label, the sorted list of prices across all suppliers (lowest to highest), treating missing prices as 999.

Later I just need the 5 lowest prices per variant, but the core issue is getting a clean, sorted array per variant at all.

Sample payload

This is what I’m sending to my Make webhook for testing:

curl -X POST "https://hook.example.com/test-webhook" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "event_group": "tender",
      "event_id": "request_awarded",
      "data": {
        "request": {
          "request_id": "REQ123ABC",
          "request_url": "https://app.example.com/customer/requests/REQ123ABC",
          "energy_type": "gas",
          "energy_type_label": "Gas",
          "energy_total": 70000,
          "pocs_count": 2,
          "flexibility_up": 15,
          "flexibility_down": -15,
          "fixed_price": true,
          "spot_market": true,
          "spot_market_share": [0],
          "tranche_model": false,
          "tranche_count": null,
          "provision": "0.5",
          "delivery_period": {
            "start": "01.01.2026",
            "end": "31.12.2026"
          },
          "date_of_award": "10.11.2025",
          "start_of_award_period": "12:00",
          "end_of_award_period": "14:00",
          "request_variants": [
            {
              "status": "rejected",
              "name": null,
              "additional_year": false,
              "eco_energy": false,
              "label": "Gas (Fossil)",
              "separate_pricing": false
            },
            {
              "status": "rejected",
              "name": null,
              "additional_year": false,
              "eco_energy": true,
              "label": "Biomethane",
              "separate_pricing": false
            },
            {
              "status": "rejected",
              "name": null,
              "additional_year": true,
              "eco_energy": false,
              "label": "Gas (Fossil) + Extra year",
              "separate_pricing": false
            },
            {
              "status": "rejected",
              "name": null,
              "additional_year": true,
              "eco_energy": true,
              "label": "Biomethane + Extra year",
              "separate_pricing": false
            }
          ]
        },
        "user": {
          "user_id": 31,
          "company_id": 14,
          "salutation": "Mr",
          "first_name": "John",
          "last_name": "Doe",
          "email": "john.doe@example.com",
          "created_at": "06.10.2025",
          "role": "customer"
        },
        "customer": {
          "company_id": 14,
          "company_name": "Customer Test Ltd",
          "contact_persons": [
            {
              "user_id": 31,
              "salutation": "Mr",
              "first_name": "John",
              "last_name": "Doe",
              "email": "john.doe@example.com",
              "role": "Editor"
            },
            {
              "user_id": 31,
              "salutation": "Mr",
              "first_name": "John",
              "last_name": "Doe",
              "email": "john.doe@example.com",
              "role": "Signer 1"
            }
          ]
        },
        "suppliers": [
          {
            "supplier_id": 11,
            "company_name": "Supplier A Ltd",
            "contact_persons": [
              {
                "user_id": 23,
                "salutation": "Mr",
                "first_name": "Alan",
                "last_name": "Smith",
                "email": "alan.smith@supplier-a.example.com",
                "role": "Editor"
              }
            ],
            "offer": {
              "variants": [
                {
                  "label": "Gas (Fossil)",
                  "status": "rejected",
                  "prices": []
                },
                {
                  "label": "Biomethane",
                  "status": "rejected",
                  "prices": []
                },
                {
                  "label": "Gas (Fossil) + Extra year",
                  "status": "rejected",
                  "prices": []
                },
                {
                  "label": "Biomethane + Extra year",
                  "status": "rejected",
                  "prices": []
                }
              ]
            },
            "status": "loser"
          },
          {
            "supplier_id": 5,
            "company_name": "Supplier B AG",
            "contact_persons": [
              {
                "user_id": 8,
                "salutation": "Ms",
                "first_name": "Emma",
                "last_name": "Brown",
                "email": "emma.brown@supplier-b.example.com",
                "role": "Editor"
              }
            ],
            "offer": {
              "variants": [
                {
                  "label": "Gas (Fossil)",
                  "status": "selected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "6",
                      "meter_type": null
                    }
                  ]
                },
                {
                  "label": "Biomethane",
                  "status": "rejected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "6",
                      "meter_type": null
                    }
                  ]
                },
                {
                  "label": "Gas (Fossil) + Extra year",
                  "status": "rejected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "5.4",
                      "meter_type": null
                    }
                  ]
                },
                {
                  "label": "Biomethane + Extra year",
                  "status": "rejected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "5.5",
                      "meter_type": null
                    }
                  ]
                }
              ]
            },
            "status": "winner"
          },
          {
            "supplier_id": 3,
            "company_name": "Supplier C GmbH",
            "contact_persons": [
              {
                "user_id": 7,
                "salutation": "Mr",
                "first_name": "Chris",
                "last_name": "Miller",
                "email": "chris.miller@supplier-c.example.com",
                "role": "Editor"
              }
            ],
            "offer": {
              "variants": [
                {
                  "label": "Gas (Fossil)",
                  "status": "rejected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "25",
                      "meter_type": null
                    }
                  ]
                },
                {
                  "label": "Biomethane",
                  "status": "rejected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "28",
                      "meter_type": null
                    }
                  ]
                },
                {
                  "label": "Gas (Fossil) + Extra year",
                  "status": "rejected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "26",
                      "meter_type": null
                    }
                  ]
                },
                {
                  "label": "Biomethane + Extra year",
                  "status": "rejected",
                  "prices": [
                    {
                      "type": "EnergyPrice",
                      "baseline": null,
                      "ct_per_kwh": "27",
                      "meter_type": null
                    }
                  ]
                }
              ]
            },
            "status": "loser"
          }
        ],
        "transaction_timestamp": "2025-11-10 12:07:32"
      }
    }
  ]'

My goal is something like this:

[
  {
    "label": "Gas (Fossil)",
    "prices": [6, 25, 999]              // sorted, 999 = missing
  },
  {
    "label": "Biomethane",
    "prices": [6, 28, 999]
  },
  {
    "label": "Gas (Fossil) + Extra year",
    "prices": [5.4, 26, 999]
  },
  {
    "label": "Biomethane + Extra year",
    "prices": [5.5, 27, 999]
  }
]

Any ideas?

Thanks

Max

Hey Max, here’s a sample scenario:

It has 3 different routes depending on what you’re looking for/how you prefer to run this.

Routes 1 & 2 are using Make Code - JavaScript code to process your webhook data array and extract the prices in the format you expect.

Route 3 doesn’t use Make code so it has a few more steps to set up but I described them below.


1. Make Code - Structured Output

  • Single module using JavaScript to process your webhook input
  • To use it, you would replace the mapped data variable with the one from your webhook (if you use my scenario directly, you won’t need to re-map)
  • Returns a parsed JSON array of labels + prices which can be mapped individually to other modules

2. Make Code - JSON Output

  • Similar to route 1, but this one returns a JSON string as an output
  • Example:
[{"label":"Gas (Fossil)","prices":[6,25,999]},{"label":"Biomethane","prices":[6,28,999]},{"label":"Gas (Fossil) + Extra year","prices":[5.4,26,999]},{"label":"Biomethane + Extra year","prices":[5.5,27,999]}]

Note: Make Code modules require (any) paid plan and cost 2 credits per 1 second of execution time. The code in your sample took ~230ms to run.


3. Tools - JSON Output

If you don’t want to use Make Code, you can achieve this with Make’s own tools - 3 credits total but the setup is a bit more complex. When you copy the scenario I shared above, some of the settings won’t get copied and you’ll have to re-create them:

So let me walk you through what you’ll need to set up, it’s 3 steps:

#1 First, click on the JSON Aggregator:

  1. Create a new data structure, call it e.g. SupplierPrices
  2. Under Specification, click Generate and paste this in:
    {"label": "test", "price": [100, 123, 100.00]}
  3. Save this configuration.

When you do the above, the module should populate with the variables I already mapped and look like this:

If it doesn’t automatically map:

  • label: map the key from the Array Aggregator module
  • prices: paste the formula {{sort(map(18.array; "price"))}} – this will sort the prices ascending

#2 Next, click on the helper JSON module at the end:

The purpose of this module is to provide a structure for the array aggregator we’ll configure last.

  1. You’ll need to create a new data structure here as well, call it PriceArrays
  2. Under Specification, click Generate and paste this in:
    {"list":[{"price": 0}]}
  3. Generate and save this collection

#3 Finally, click on the Array Aggregator to the left of the JSON aggregator:

  1. Change Target structure type to list
  2. The other fields should populate with the variables and formulas from my template, but if they don’t, paste this in the price box:
    {{parseNumber(if(length(map(3.prices; "ct_per_kwh")); first(map(3.prices; "ct_per_kwh")); 999))}}

So now the setup is done - if you run the scenario, all routes should run and you’ll see the outputs at the end of each.

I added notes to the modules in the last route to explain what each module is used for.

If you get stuck anywhere, let me know.

Cheers!

3 Likes

Cheers to @Max16 for such a well-structured question and @SierraV for the amazing response!

Many will learn from this one!

1 Like

sooooo coool… thank you!