Skip to content

Getting Started

Creating an application

After signing up here and go to the developers link at the dashboard

Fill the name and the redirectURI for your application

Application

After created write down the clientId and ClientSecret of your application.

Authorization Code flow

When you create an application, you will receive a client_id for Authorization Code Flow, you will also need to specify a Redirect Uri. This is the url that the user will be redirected to after the flow.

By sending your user through the Invoce Processor Authorization flow, you can get permission to access their data. They will signup or login to an existing accoun.


sequenceDiagram
    participant User
    participant PartnerApp
    participant InvprocAPI
    participant InvprocPartnerLogin


    participant PartnerCallBack
    participant PartnerRedictPage
    participant InvProcAPI

    User->>PartnerApp: Login
    PartnerApp->>InvprocAPI: POST/auth credentials state code_challenge
    InvprocAPI->>InvprocAPI: validate credentials
    InvprocAPI->>InvprocAPI: encrypt login URL (state and code_challlenge)
    InvprocAPI->>PartnerApp: login URL 307
    PartnerApp->>User: login URL 307
    User->>InvprocPartnerLogin: login
    InvprocPartnerLogin->>InvprocPartnerLogin: handle Login
    InvprocPartnerLogin->>PartnerApp: authorization code + state
    PartnerApp->>InvprocAPI: GET/token with authorization_code code_verifier
    InvprocAPI->>PartnerApp: access and refresh token 
    PartnerApp->>InvProcAPI: do API calls on behalf of the user

The Login URL call (the auth call)

Initiate the Authorization code flow. To be able to initiate the Authorization flow, first the application needs a state and code_verifier which is a randomly generate, high entropy string between 43 and 128 characters. Store it, you'll need it later to fetch the access_token and to verify againt CSRF.

A successful response will be a temporary redirect to the Invoice Processor Login screen.

Please refer to the API docs for futher details

Below a fully functional example with react server action

  • Run NextJs boilder app
  • in the app folder create two folders one for login and one for callback, you are free to use any name as long as the callback one matches the redirectURI (i.e. if you named the folder callback the redirect URI will be http://localhost:3000/callback)
  • Copy both files action.ts and page.tsx to the login folder and both files at the callback section to the callback folder
'use server';

import crypto from 'crypto';
import { cookies } from 'next/headers';

// Helper functions
function generateCodeVerifier() {
  return crypto.randomBytes(50).toString('hex');
}

function generateCodeChallenge(codeVerifier: string) {
  return crypto
    .createHash('sha256')
    .update(codeVerifier)
    .digest('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function generateState(length: number = 32): string {
  return crypto.randomBytes(length)
    .toString("base64url")
    .slice(0, length);
}

export async function initiateAuthFlow() {
  const clientId = 'YOUR CLIENT ID HERE';
  const clientSecret = 'YOUR CLIENT SECRET HERE';

  // Generate and store the code verifier and state
  const codeVerifier = generateCodeVerifier();
  const codeChallenge = generateCodeChallenge(codeVerifier);
  const state = generateState(32);

  // Store these values in cookies so they can be retrieved later
  // THIS IS FOR DEMONSTRATION PURPOSES ONLY DO STORE AUTHENTICATION INFO ON THE CLIENT SIDE, USE SERVER SIDE SESSIONS IN PRODUCTION
  const cookieStore = cookies();
  (await cookieStore).set('codeVerifier', codeVerifier, { 
    httpOnly: true, 
    secure: process.env.NODE_ENV === 'production',
    maxAge: 60 * 10, // 10 minutes
    path: '/'
  });

  (await cookieStore).set('state', state, { 
    httpOnly: true, 
    secure: process.env.NODE_ENV === 'production',
    maxAge: 60 * 10, // 10 minutes
    path: '/'
  });

  try {
    const response = await fetch('https://api.invproc.com/api/oauth2/auth', {
      method: 'POST',
      redirect: 'manual', // Prevent automatic following of redirects
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ 
        client_id: clientId, 
        client_secret: clientSecret, 
        code_challenge: codeChallenge, 
        state: state, 
        isSignup: false 
      })
    });

    if (response.status === 302 || response.status === 307) {
      const location = response.headers.get('Location');
      if (location) {
        return { success: true, redirectUrl: location };
      }
    }

    return { 
      success: false, 
      error: `Unexpected response status: ${response.status}` 
    };
  } catch (error) {
    console.error('Auth flow initiation error:', error);
    return { 
      success: false, 
      error: 'Failed to initiate authentication flow' 
    };
  }
} 
'use client';

import { useEffect, useState } from 'react';
import { initiateAuthFlow } from './actions';

export default function PartnerTest() {
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const startAuth = async () => {
      try {
        const result = await initiateAuthFlow();

        if (result.success && result.redirectUrl) {
          // Redirect the user to the authentication URL
          window.location.href = result.redirectUrl;
        } else {
          setError(result.error || 'Unknown error occurred');
          setIsLoading(false);
        }
      } catch (err) {
        console.error('Error starting auth flow:', err);
        setError('Failed to start authentication process');
        setIsLoading(false);
      }
    };

    startAuth();
  }, []);

  if (isLoading) {
    return <div>Initializing authentication...</div>;
  }

  if (error) {
    return <div>Error: {error}</div>;
  }

  return <div>Partner Test</div>;
}

The Callback

After the User sucessfully completes the Login the configured redirect URI is going to be called with two url parameters code and state.

the code parameter

This it the authorization code that should be use to exchange for the authorization tokens (access and refresh tokens)

verify the state parameter

Please make sure to verify the received state parameter against the one you stored previously

//get the tokens from the server action
'use server';
import { cookies } from "next/headers";

export async function getTokens(code: string, state: string) {

    // read state and codeVerifier from cookies
    const cookieStore = await cookies();
    const codeVerifier = cookieStore.get('codeVerifier');
    const clientId = 'YOUR CLIENT ID HERE';
    const clientSecret = 'YOUR CLIENT SECRET HERE';
    const storedState = cookieStore.get('state');     
    if (!codeVerifier || !storedState) {
        throw new Error('No codeVerifier or state found');
    }
    // VERIFY STATE TO CHECK AGAINST CSRF ATTACKS
    if (storedState.value !== state) {
        throw new Error('Invalid state');
    }
//const { client_id, client_secret, authorization_code, code_verifier } = req.body;
    const response = await fetch(`https://api.invproc.com/api/oauth2/tokens`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ client_id: clientId, client_secret: clientSecret, authorization_code: code, code_verifier: codeVerifier.value }),
    });
    if (!response.ok) {
        throw new Error('Failed to fetch tokens');
    }else{
        const data = await response.json();
        return data;
    }
}
'use client';
import { useSearchParams } from "next/navigation";
import { getTokens } from "./actions";
import { Suspense, useEffect, useState } from "react";


function RenderResult(){
    const searchParams = useSearchParams();
    const code = searchParams.get('code')||'';
    const state = searchParams.get('state')||'';
    const [accessToken, setAccessToken] = useState<string | null>(null);
    const [refreshToken, setRefreshToken] = useState<string | null>(null);

    useEffect(() => {
        getTokens(code, state).then((tokens) => {            
            setAccessToken(tokens.accessToken);
            setRefreshToken(tokens.refreshToken);
        });
    }, [code, state]);
    return <div>Access Token: {accessToken} Refresh Token: {refreshToken}</div>;
}

export default function PartnerTestCallback() {



        return (
            <Suspense fallback={<div>Loading...</div>}>
                <RenderResult />
            </Suspense>
        )


}

The Token exchange

Call the token exchange end point with your client credentials, the received authorization code and the previously stored code verifier