How to migrate content using a web service with multiple calls? I need to migrate content from a web service to which I have to call several URLs. A call to receive a list of nodes and then a call for each node to receive information from the fields.

Something like this:

Calling domain.test/webservice/

&lt;results&gt;
  &lt;count&gt;10&lt;/count&gt;
  &lt;list&gt;
    &lt;id&gt;100&lt;/id&gt;
    &lt;id&gt;101&lt;/id&gt;
    &lt;id&gt;102&lt;/id&gt;
    ...
  &lt;/list&gt;
&lt;/results&gt;


Calling domain.test/webservice/101

&lt;results&gt;
  &lt;id&gt;101&lt;/id&gt;
  &lt;name&gt;Name&lt;/name&gt;
  &lt;field1&gt;Field example&lt;/field1&gt;
    ...
&lt;/results&gt;


So, in my migration yml file I have something like:

source:
  plugin: url
  data_fetcher_plugin: http
  headers:
    Accept: application/xml
  data_parser_plugin: simple_xml
  urls: https://domain.test/webservice/


How can I specified the migrate process to retrieve each content info from a URL like https://domain.test/webservice/[ID]?
There is an issue with the UrlPerRow plugin above. 

The source data isn't populated until after the check for whether the source data has changed. Because of that fact, it's always looking at previous data, which means the hash will never change, so "rowChanged()" never returns true. It will import new data but not update if the data changes. 

A fixed version is below:

&lt;?php

namespace Drupal\fad_migration\Plugin\migrate\source;

use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
use Drupal\migrate_plus\Plugin\migrate\source\Url;
use Drupal\migrate\Row;

/**
 *
 * @MigrateSource(
 *   id = "url_per_row"
 * )
 */
class UrlPerRow extends Url {

  protected $dataNewParserPlugin;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
  }

  /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    $prepare_row = parent::prepareRow($row);

    $item_info_settings = $row-&gt;getSourceProperty('item_info');
    if (!empty($item_info_settings)) {
      $this-&gt;getItemData($row, $item_info_settings);
    }

    if (!$row-&gt;getIdMap() || $row-&gt;needsUpdate() || $this-&gt;aboveHighwater($row) || $this-&gt;rowChanged($row)) {
      return $prepare_row;
    }
    else {
      return false;
    }

  }

  protected function getItemData(Row &amp;$row, $settings) {

    $new_url = str_replace($settings['token'], $row-&gt;getSourceProperty($settings['token_replace']), $settings['url']);
    $this-&gt;getNewDataParserPlugin($new_url, $settings['item_selector'], $row-&gt;getSource());
    $this-&gt;dataNewParserPlugin-&gt;rewind();
    $xml_data = $this-&gt;dataNewParserPlugin-&gt;current();
    if (!empty($xml_data)) {
      $fields = $row-&gt;getSourceProperty('fields');
      foreach ($fields as $field) {
        if (!empty($field['item_info']) &amp;&amp; !empty($xml_data[$field['name']])) {
          $row-&gt;setSourceProperty($field['name'], $xml_data[$field['name']]);
        }
      }
    }    
  }

  protected function getNewDataParserPlugin($url, $item_selector, $settings) {
    $settings['urls']          = [$url];
    $settings['item_selector'] = $item_selector;

    $this-&gt;dataNewParserPlugin = \Drupal::service('plugin.manager.migrate_plus.data_parser')
                                        -&gt;createInstance($settings['data_parser_plugin'], $settings);

    return $this-&gt;dataNewParserPlugin;
  }

}