---
title: "Customer Portal Development"
canonical: https://wavect.io/services/customer-portals/
language: en
description: "Plan and build customer portals around documents, requests and account access. Define roles, system connections, a first release and operating ownership."
image: "https://wavect.io/img/general/bak/open_graph_preview.jpg"
---

Customer Portal Development

# Give customers a clear place to complete routine requests.

Replace a recurring email exchange with a useful customer journey: find a document, check a request or submit the information your team needs. Start with one task worth making self-service.

EXAMPLE FIRST RELEASE

## A customer gets the right document without emailing you.

1. **Identify the account** Sign in and establish which company the person belongs to.
2. **Complete one task** Find a document
3. **Keep the boundary** Enforce company access on every request and handle revoked access.

DIRECT ANSWER

A customer portal is an authenticated area for external customers or partners. Its scope follows their tasks, organization boundaries and the systems that own the underlying information.

Best for

- Customers repeatedly asking staff for the same information
- A business with clear account and permission rules

Consider another approach

- A standard customer area already meets the need
- An employee-only workflow, better scoped as an internal tool

// 01

## Choose the portal size by the task

A simple customer area might expose a document and one request form. A B2B portal may need multiple companies, account administrators and invitations. A broader platform adds workflows and operational responsibilities. Check an existing product before commissioning custom work; the [Power Pages cost comparison](/blog/power-pages-vs-custom-customer-portal-cost/) helps frame that decision without treating a calculator as a project quote. Use the [custom software versus off-the-shelf guide](/software-development-guide/custom-software-vs-off-the-shelf/) to decide whether configuration is enough.

// 02

## Define a complete journey, not a collection of dashboard tiles

For a document portal, success is a customer finding the correct current document and understanding what to do when it is missing. The first release must cover that whole path, including the administration your team needs to keep it working.

A journey and state map

Map invitation, first sign-in, document access, an empty account and a failed request. Include mobile and keyboard use, not just the ideal desktop screen.

A permission matrix

List actions for customer viewers, company administrators and internal operators. Separate company membership from global staff access, including how a person changes company or loses access.

A source-system contract

Identify which system owns each document and status. Agree refresh behavior, visible timestamps and the customer message when the source is unavailable or the result is not ready.

A usable administration path

Specify who invites, suspends and supports users. Include an access review and a trace of administrative changes so support can diagnose a problem without sharing customer passwords.

// 03

## Illustrative roles: separate companies before adding features

Synthetic scope example: a customer viewer reads documents for their own company; a customer administrator invites colleagues within that company; an internal operator resolves requests under explicitly granted access. Test cross-company reads and writes, invitation expiry, account removal and direct document URLs. Hiding a button is not an authorization boundary.

// 04

## Connect the portal to the source of truth

Map customers, documents and request status to their owning CRM, ERP or document store. Decide what a user sees when information is stale or a source is unavailable. [API integration](/services/api-integration/) covers system-to-system connections; the portal owns the customer-facing journey. The [Bond Analytics case](/case-studies/bond-analytics/) shows adjacent web-application engineering, not proof of a customer-portal rollout.

| Journey to test | Expected result | Failure that must be prevented |
| --- | --- | --- |
| Viewer requests their company invoice | Return an authorized document from the owning system. | A stale or incorrectly mapped account document. |
| Viewer guesses another company document URL | Deny access without exposing its contents. | Cross-company access through a direct API request. |
| Administrator invites a colleague | Limit the invitation to their own company and agreed lifetime. | An invitation granting global or permanent access. |
| Customer access is revoked | Enforce revocation on subsequent requests. | A cached session continuing to retrieve restricted files. |

// 05

## Review the portal as a customer and as its operator

A good-looking front end is only part of the release. Use representative roles and documents to verify the journey, then let the internal operator run the everyday support tasks before the first customer is invited.

1. **Choose one high-friction request** - Use recurring support conversations to identify a useful self-service task. Measure the existing steps and decide what the customer should still be able to escalate to a person.
2. **Review a working vertical slice** - Connect sign-in, the real permission rule and one source-system response. Test empty, denied and unavailable states before expanding the feature list.
3. **Introduce customers deliberately** - Agree invitation ownership, account-support guidance and a rollback path. Check completion and support feedback before adding another journey.

// 06

## Agree the first release and its handover

Bring one complete journey, sample roles, source-system constraints and the person who accepts the result. Define success and error states, accessible forms and the agreed test coverage before implementation. Review a working version, verify access boundaries, then document administration and release responsibilities. Licensing, hosting, source-system access and maintenance affect operating costs separately from the build. [Web Apps](/services/web-apps/) remains the parent for broader product work.

// proof

## Account-based application engineering

Bond Analytics demonstrates authenticated application and data-workflow engineering. The portal scope starts with your customer task and company-access model, rather than assuming an existing case is the same product.

### [Bond Analytics Platform](/case-studies/bond-analytics/)
- **Status:** Live
- **Outcome:** 2 months - 0 to Enterprise
- **Summary:** Institutional bond analytics platform, zero to enterprise-ready in 2 months; raised multiple rounds.
- **Stack:** NestJS, React, Typescript

These are selected projects, not our full portfolio. We have shipped 75+ products since 2018.

## Questions before you start

### Can it connect to our CRM or ERP?

Assess the available API or supported export, data rights and the required freshness first. The portal should not silently become a second master customer database.

### What belongs in the first release?

One useful end-to-end customer task, the necessary account controls, clear failure states and an agreed acceptance check. Add further tasks when the first journey is useful.

### What happens after handover?

Agree who manages accounts, content, hosting, dependencies and customer support. Ownership and support terms belong in the agreed scope, not an assumed portal package.

## Continue with the decision you need

- [Broader web-application development](/services/web-apps/)
- [Connecting the portal to existing systems](/services/api-integration/)
- [Power Pages versus a custom portal](/blog/power-pages-vs-custom-customer-portal-cost/)

## Structured Data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@id": "https://wavect.io/#organization",
      "@type": [
        "Organization",
        "ProfessionalService",
        "LocalBusiness"
      ],
      "employee": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "founder": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "legalRepresentative": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "name": "Wavect GmbH",
      "subjectOf": {
        "@id": "https://wavect.io/verified-claims.json#dataset",
        "@type": "Dataset",
        "creator": {
          "@id": "https://wavect.io/#organization",
          "@type": [
            "Organization",
            "ProfessionalService",
            "LocalBusiness"
          ]
        },
        "description": "A machine-readable registry of quantitative and qualitative claims published by Wavect, with review dates, localized page appearances and public third-party citations where available.",
        "inLanguage": "en",
        "isAccessibleForFree": true,
        "license": "https://creativecommons.org/licenses/by/4.0/",
        "name": "Wavect verified publication claims",
        "url": "https://wavect.io/verified-claims.json"
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/team/kevin-riedl/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Kevin Riedl",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796365",
        "https://www.linkedin.com/in/wsdt",
        "https://github.com/wsdt"
      ],
      "url": "https://wavect.io/team/kevin-riedl/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/team/christof-jori/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Christof Jori",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796367",
        "https://www.linkedin.com/in/jocr77/",
        "https://github.com/jo-chris"
      ],
      "url": "https://wavect.io/team/christof-jori/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/#website",
      "@type": "WebSite",
      "inLanguage": [
        "en",
        "de",
        "es",
        "zh"
      ],
      "name": "Wavect",
      "potentialAction": {
        "@type": "SearchAction",
        "query-input": "required name=search_term_string",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://wavect.io/search/?q={search_term_string}"
        }
      },
      "publisher": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      },
      "url": "https://wavect.io/"
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@id": "https://wavect.io/services/customer-portals/#service",
      "@type": "Service",
      "areaServed": [
        {
          "@type": "Country",
          "name": "Austria"
        },
        {
          "@type": "Place",
          "name": "Worldwide"
        }
      ],
      "category": "Software Development",
      "dateModified": "2026-10-19",
      "description": "Plan and build customer portals around documents, requests and account access. Define roles, system connections, a first release and operating ownership.",
      "name": "Give customers a clear place to complete routine requests.",
      "provider": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      },
      "serviceType": "Customer Portal Development",
      "url": "https://wavect.io/services/customer-portals/"
    },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "item": "https://wavect.io/",
          "name": "Home",
          "position": 1
        },
        {
          "@type": "ListItem",
          "item": "https://wavect.io/services/overview/",
          "name": "Services",
          "position": 2
        },
        {
          "@type": "ListItem",
          "item": "https://wavect.io/services/overview/#software",
          "name": "Software Development",
          "position": 3
        },
        {
          "@type": "ListItem",
          "item": "https://wavect.io/services/customer-portals/",
          "name": "Customer Portal Development",
          "position": 4
        }
      ]
    },
    {
      "@id": "https://wavect.io/services/customer-portals/#webpage",
      "@type": "WebPage",
      "dateModified": "2026-10-19",
      "description": "Plan and build customer portals around documents, requests and account access. Define roles, system connections, a first release and operating ownership.",
      "inLanguage": "en",
      "isPartOf": {
        "@id": "https://wavect.io/#website",
        "@type": "WebSite"
      },
      "mainEntity": {
        "@id": "https://wavect.io/services/customer-portals/#service"
      },
      "name": "Give customers a clear place to complete routine requests.",
      "url": "https://wavect.io/services/customer-portals/"
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Assess the available API or supported export, data rights and the required freshness first. The portal should not silently become a second master customer database."
      },
      "name": "Can it connect to our CRM or ERP?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "One useful end-to-end customer task, the necessary account controls, clear failure states and an agreed acceptance check. Add further tasks when the first journey is useful."
      },
      "name": "What belongs in the first release?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Agree who manages accounts, content, hosting, dependencies and customer support. Ownership and support terms belong in the agreed scope, not an assumed portal package."
      },
      "name": "What happens after handover?"
    }
  ],
  "speakable": {
    "@type": "SpeakableSpecification",
    "cssSelector": [
      ".faq-question",
      ".faq-answer"
    ]
  }
}
```
