# Ignition 8.3: Guidance/example on end-to-end WebUI / Gateway interaction

**URL:** <https://forum.inductiveautomation.com/t/ignition-8-3-guidance-example-on-end-to-end-webui-gateway-interaction/113054>\
**Category:** Module Development\
**Tags:** ignition83\
**Created:** [January 2, 2026, 4:44pm UTC](https://forum.inductiveautomation.com/t/ignition-8-3-guidance-example-on-end-to-end-webui-gateway-interaction/113054 "2026-01-02T16:44:12Z")\
**Posts on this page:** 1\
**Showing post:** 6

<div class="post-metadata">

**Author:** ![mpderbec](https://avatars.discourse-cdn.com/v4/letter/m/bbe5ce/32.png) [@mpderbec](https://forum.inductiveautomation.com/u/mpderbec)\
**Post date:** [January 3, 2026, 2:08pm UTC](https://forum.inductiveautomation.com/t/ignition-8-3-guidance-example-on-end-to-end-webui-gateway-interaction/113054/6 "2026-01-03T14:08:31Z")

</div>

OK thanks for the input, I was able to get everything going.

@pturmel’s pointer to [8.3 File Upload route permissions issue - #8 by pturmel](https://forum.inductiveautomation.com/t/8-3-file-upload-route-permissions-issue/112007/8) was really key for posting – i.e., whenever you use `PermissionType.WRITE`. I was able to access the `ReduxRootState` exactly as he did. Otherwise you will get a non-descript 403 Forbidden error.

For those following along and looking for more examples of what works, here’s what my `mountRouteHandlers()` looked like in my GatewayHook class:

```python
    public void mountRouteHandlers(RouteGroup routes) {
        routes.newRoute("/settings")
                .type(RouteGroup.TYPE_JSON)
                .handler(this::getSettingsJson)
                .method(HttpMethod.GET)
                .requirePermission(PermissionType.READ)
                .mount();

        routes.newRoute("/settings")
                .type(RouteGroup.TYPE_JSON)
                .handler(this::putSettingsJson)
                .method(HttpMethod.PUT)
                .requirePermission(PermissionType.WRITE)
                .mount();
    }

```

and here’s what my `Settings.service.ts` looks like on the frontend:

```python
export const getSettings = async (): Promise<SeeqSettings> => {
  const response = await fetch("/data/seeq/settings", {
    method: "GET",
    headers: {
      Accept: "application/json",
    },
  });

  if (!response.ok) {
    throw new Error(`Failed to fetch settings: ${response.statusText}`);
  }

  const payload: SeeqSettingsPayload = await response.json();

  // Flatten the structure for UI convenience
  return {
    enabled: payload.enabled,
    ...payload.config,
  };
};

export const updateSettings = async (
  settings: Partial<SeeqSettings>,
  csrfToken: string
): Promise<UpdateSettingsResponse> => {
  // Extract enabled and wrap the rest in config
  const { enabled, ...config } = settings;

  const payload: Partial<SeeqSettingsPayload> = {
    enabled,
    config: config as IgnitionModuleConfigurationV2,
  };

  const response = await fetch("/data/seeq/settings", {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      "X-CSRF-Token": csrfToken,
    },
    body: JSON.stringify(payload),
  });

  const data: UpdateSettingsResponse = await response.json();

  // Check if the operation was successful
  if (!data.success) {
    throw new Error(data.message || "Failed to update settings");
  }

  return data;
};

```

where `csrfToken` was retrieved via this code:

```python
  const csrfToken = useSelector(
    (state: ReduxRootState) => state.userSession.csrfToken || ""
  );

```

---

_[View the full topic](https://forum.inductiveautomation.com/t/ignition-8-3-guidance-example-on-end-to-end-webui-gateway-interaction/113054)._
