> ## Documentation Index
> Fetch the complete documentation index at: https://developers.ingopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Single Sign-On

> Hand an already-authenticated customer from your portal directly into the Digital Payment Center with a signed redirect — bypassing the recipient authentication challenge. Available for Notify — Classic and Notify — Managed Parties.

Single Sign-On (SSO) lets you authenticate your own customer in your own portal and hand them directly into the Digital Payment Center (DPC). The customer arrives already authenticated, so the DPC does not present the [Recipient Authentication](/docs/hub/product-risk-configurations) challenge.

SSO is an authentication method in its own right, configured per participant as an alternative to Recipient Authentication questions or a One-Time Authorization Code. Every party to a payment must satisfy the configured authentication method individually.

<Info>
  This is a **signed-URL redirect**, not a federated identity integration. There is no SAML metadata exchange, no identity-provider configuration, no directory integration and no certificate rotation. You sign a URL with a shared secret and issue a redirect.
</Info>

<Note>
  The signing scheme on this page is **not** the same as the HMAC request signing used for Ingo APIs. API requests are signed over a header signature string — see [Authentication](/docs/hub/authentication). SSO is signed over the request URL itself, as described below. Confirm with your Integration Manager whether your SSO credentials are the same pair as your API credentials.
</Note>

***

## When to use it

<CardGroup cols={2}>
  <Card title="Good fit">
    Your recipients already sign in to a portal you operate, and that portal is close enough to the system issuing disbursements to carry a signed link.
  </Card>

  <Card title="Use authentication questions instead">
    Recipients have no portal relationship with you, or a disbursement may reach someone who has stopped logging in — for example a closed or cancelled account.
  </Card>
</CardGroup>

You can offer both. A notification can invite the recipient to sign in *or* to continue through the standard challenge.

***

## How it works

<Steps>
  <Step title="Stage the disbursement">
    Call Notify server-to-server. The response returns a `notification_id`. For Notify — Managed Parties it also returns a `parties` array, each entry carrying a `party_id`.
  </Step>

  <Step title="Your customer signs in to your portal">
    Authentication happens entirely on your side, using whatever method you already use.
  </Step>

  <Step title="Build and sign the redirect URL">
    Assemble the URL, compute the signature, and append it. See [Hash generation](#hash-generation).
  </Step>

  <Step title="Redirect the customer">
    Issue the redirect from your application server. The customer lands in the DPC already authenticated and selects how to be paid. No authentication challenge is presented.
  </Step>

  <Step title="Handle the callback">
    On completion, cancellation or error, Ingo redirects the customer to your return URL with a status and sub-status, which you may verify against the same signature scheme.
  </Step>
</Steps>

***

## Multi-party disbursements

Notify — Managed Parties addresses entry per party, so the SSO URL carries a party identifier in addition to the notification identifier.

<Warning>
  **`partyId` is required on Notify — Managed Parties**, including when the disbursement has a single recipient. The party is resolved by exact match — a request without `partyId` will not resolve to a party.
</Warning>

Both **Recipient** and **Approver** parties may enter by SSO. Interested parties do not enter the DPC and are not issued an entry URL.

Because every party authenticates in their own right, the handoff is performed **once per party**, not once per payment. A disbursement with a recipient and an approver requires two separate signed redirects, each carrying that party's own `partyId`.

***

## Request

<CodeGroup>
  ```text Notify — Classic theme={null}
  https://{domain}/session/sso/{notification_id}
    ?t={timestamp}
    &return_url={return_url}
    &nonce={nonce}
    &username={username}
    &h={hash}
  ```

  ```text Notify — Managed Parties theme={null}
  https://{domain}/session/sso/{notification_id}
    ?t={timestamp}
    &return_url={return_url}
    &nonce={nonce}
    &username={username}
    &h={hash}
    &partyId={party_id}
  ```
</CodeGroup>

### URL values

| Value             | Description                                                                                 |
| ----------------- | ------------------------------------------------------------------------------------------- |
| `domain`          | The default or custom domain of your hosted DPC. See [Domain Setup](/docs/hub/domain-setup) |
| `notification_id` | The notification identifier returned by the Notify staging call                             |

### Parameters

| Parameter    | Description                                                                                                                                                                        |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `t`          | Timestamp of signature generation. Epoch Unix timestamp in seconds                                                                                                                 |
| `return_url` | The authorized return URL the customer is sent back to. URL-encode this value                                                                                                      |
| `nonce`      | An arbitrary or random value returned to you unchanged in the callback. Used for additional validation of incoming messages                                                        |
| `username`   | Your provisioned client username                                                                                                                                                   |
| `h`          | The digital signature of the request                                                                                                                                               |
| `partyId`    | **Notify — Managed Parties only, required.** The `party_id` of the party signing in, from the `parties` array of the Notify response. This is `party_id`, not `client_provided_id` |

<Warning>
  **The signed request is short-lived.** The timestamp is validated on receipt and a request older than a short window, measured in seconds, is rejected. Generate the timestamp and issue the redirect in the same request cycle. Do not pre-generate SSO URLs, cache them, or deliver them by email or SMS. Confirm the exact window with your Integration Manager.
</Warning>

***

## Hash generation

<Steps>
  <Step title="Generate a timestamp and encode your return URL">
    ```csharp theme={null}
    var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    var return_url = HttpUtility.UrlEncode("https://pay.example.com/callback");
    ```
  </Step>

  <Step title="Create the request URL">
    ```csharp theme={null}
    var nonce = Guid.NewGuid();
    var requestUrl = $"{domain}/session/sso/{notification_id}"
        + $"?t={timestamp}&return_url={return_url}"
        + $"&nonce={nonce}&username={username}";
    ```

    Do **not** include `partyId` in the string you sign. It is appended after the hash — see [URL redirect](#url-redirect).
  </Step>

  <Step title="Convert the request URL to lowercase">
    ```csharp theme={null}
    requestUrl = requestUrl.ToLower();
    ```
  </Step>

  <Step title="Compute the SHA-512 hash">
    ```csharp theme={null}
    static byte[] ComputeSha512Hash(string value)
    {
        using (var sha512Managed = new SHA512Managed())
        {
            var content = Encoding.UTF8.GetBytes(value);
            return sha512Managed.ComputeHash(content);
        }
    }

    var sha512HashedContent = ComputeSha512Hash(requestUrl);
    ```
  </Step>

  <Step title="Compute the HMAC-SHA512 signature and Base64-encode it">
    ```csharp theme={null}
    static byte[] ComputeHmacHash(byte[] hashedContent, string secret)
    {
        byte[] secretBytes = Encoding.UTF8.GetBytes(secret);
        using (HMACSHA512 hmacsha512 = new HMACSHA512(secretBytes))
        {
            return hmacsha512.ComputeHash(hashedContent);
        }
    }

    var hashBytes = ComputeHmacHash(sha512HashedContent, secret);
    var hash = Convert.ToBase64String(hashBytes);
    ```
  </Step>
</Steps>

<Warning>
  Use **RAW** output from your hashing and HMAC libraries — not hex. Base64-encoding hex output produces an incorrect signature.
</Warning>

***

## URL redirect

<Steps>
  <Step title="Append the signature">
    ```csharp theme={null}
    requestUrl = $"{requestUrl}&h={hash}";
    ```
  </Step>

  <Step title="Append the party identifier — Managed Parties only">
    ```csharp theme={null}
    requestUrl = $"{requestUrl}&partyId={party_id}";
    ```
  </Step>

  <Step title="Issue the redirect">
    ```csharp theme={null}
    return Redirect(requestUrl);
    ```
  </Step>
</Steps>

**Example — Notify — Managed Parties:**

```text theme={null}
https://digitalpay.example.com/session/sso/1000001?t=1561045723&return_url=https%3A%2F%2Fpay.example.com%2Fcallback&nonce=PARTNER-REF-001&username=00000000-0000-0000-0000-000000000001&h=ootG56o3IrO8jMhigVb-uO11P9tpPvTzAGKtZTjPQELMXtEB7SOXiNs4x8zifYtsBUNEaW3lDJcn8of1DUoW8Q&partyId=00000000-0000-0000-0000-000000000002
```

***

## Response callback

```text theme={null}
{return_url}?notification_id={notification_id}
  &status={status}
  &substatus={substatus}
  &t={timestamp}
  &nonce={nonce}
  &username={username}
  &h={hash}
```

| Parameter         | Description                                                        |
| ----------------- | ------------------------------------------------------------------ |
| `notification_id` | The notification identifier provided in the SSO request            |
| `status`          | Status code indicating the type of event that occurred             |
| `substatus`       | Contextual status code providing additional detail                 |
| `t`               | Timestamp of signature generation. Epoch Unix timestamp in seconds |
| `nonce`           | The nonce provided in the SSO request, returned unchanged          |
| `username`        | Your provisioned client username                                   |
| `h`               | The digital signature of the request                               |

<Note>
  The callback does **not** echo `partyId`. Where you need to correlate a callback to a specific party, carry that correlation in the `nonce`.
</Note>

### Validating the callback signature

As an additional layer of security, you may validate the hash on the response. Perform hash generation steps 1–5 above, using the response URL to build the request URL in step 2 **without** the `h` parameter, then confirm the two hashes are identical.

```csharp theme={null}
var requestUrl = $"{return_url}?notification_id={notification_id}"
    + $"&status={status}&substatus={substatus}"
    + $"&t={timestamp}&nonce={nonce}&username={username}";
```

***

## Status codes

| Code   | Meaning                                             | Sub codes                                                                                                                                                                                                                                                                                           |
| ------ | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `2200` | Session completed and funded successfully           | `201` Card · `202` ACH · `203` Check · `204` PayPal                                                                                                                                                                                                                                                 |
| `2400` | Session was unsuccessful                            | `401` Configuration error · `402` Transaction canceled, terminated or expired · `403` Transaction already completed · `404` Web session expired · `405` Terminated — max account verification attempts exceeded · `406` Terminated — max processing attempts exceeded · `407` Suspended due to OFAC |
| `2401` | An authorization error has occurred                 | —                                                                                                                                                                                                                                                                                                   |
| `2404` | The customer experienced a 404 Page Not Found error | —                                                                                                                                                                                                                                                                                                   |
| `2500` | The customer experienced a system error             | —                                                                                                                                                                                                                                                                                                   |

<Warning>
  `405` and `406` mean the disbursement has been **terminated and cannot be resumed**. Treat it as closed. If the payee still needs to be paid, stage a new disbursement or fall back to another payment method.
</Warning>

<Note>
  These are SSO callback codes and are distinct from the API status codes returned in a transaction response. See [Status Codes](/docs/hub/errors).
</Note>

***

## Prerequisites

* A portal of your own that authenticates the customer before the handoff.
* A provisioned client username and secret for SSO signing.
* A return URL registered with Ingo in advance. Requests carrying an unregistered return URL are rejected.
* A server-side endpoint that builds and signs the redirect — the secret must never reach the browser.
* You are already live or in-flight on Notify — Classic or Notify — Managed Parties. SSO is a feature add, not a standalone integration.

***

## Testing

During UAT, validate the full round trip: that a signed redirect lands the customer in the DPC with no authentication challenge presented, that the callback returns to your return URL with the expected `status` and `substatus`, and that your hash validation accepts a genuine callback. Confirm your redirect is issued inside the timestamp window, and test the failure paths — an expired timestamp and a tampered signature should both be rejected.

For Notify — Managed Parties, test each party role separately. Confirm a recipient and an approver can each enter with their own `partyId`, and that the approval completes end to end. Your Integration Manager provides a test plan and validation support.
