---
title: "Primeiros passos"
description: "Primeiros passos com o CloudHub em C#, PHP, Python, Node.js e Java: obtenha uma chave de API, instale um cliente, crie uma sessão, envie o usuário a um provedor, leia o certificado e assine um hash."
canonical: "https://docs.lacunasoftware.com/articles/cloudhub/get-started"
locale: "pt-BR"
product: "CloudHub"
last_updated: "2026-10-12"
---

# Primeiros passos

Esta página vai da chave de API até um hash assinado: instale um cliente, crie uma sessão, envie o
usuário a um provedor e use a sessão que volta. Cada passo tem código em C#, PHP, Python, Node.js e
Java. [Como funciona](https://docs.lacunasoftware.com/articles/cloudhub/how-it-works) explica o fluxo por trás dos passos.

O código assina um hash calculado pela sua biblioteca de assinatura. [Assinando um PDF](https://docs.lacunasoftware.com/articles/cloudhub/pades-signature)
e [Autenticação](https://docs.lacunasoftware.com/articles/cloudhub/authentication) mostram handlers completos, que calculam esse hash e finalizam a
assinatura com PKI SDK, Rest PKI ou PKI Express.

## 1. Obter uma chave de API

Peça ao suporte da Lacuna Software (suporte@lacunasoftware.com) uma chave de API do CloudHub e
informe as URIs de redirecionamento que a sua aplicação vai usar, em produção e em desenvolvimento.
Se você for acrescentar os seus próprios parâmetros à URI de redirecionamento, como os exemplos
abaixo fazem com `fileId`, peça uma entrada de host, como `https://app.example.com`, em vez de uma
URI exata. Veja [URIs de redirecionamento](https://docs.lacunasoftware.com/articles/cloudhub/how-it-works#uris-de-redirecionamento).

Os exemplos leem a chave da variável de ambiente `CLOUDHUB_API_KEY`. Mantenha-a no servidor.

## 2. Instalar um cliente

**C#**

```bash
dotnet add package Lacuna.Cloudhub.Client
```

Para a injeção de dependência do ASP.NET Core, adicione também o `Lacuna.Cloudhub.ClientCore`.

**PHP**

```bash
composer require lacuna/cloudhub-client
```

O cliente precisa do PHP 7.4 ou posterior e usa o Guzzle.

**Python**

```bash
pip install requests
```

Os exemplos em Python chamam a API REST diretamente; são só oito operações.

**Node.js**

```bash
npm install cloudhub-client
```

**Java**

```groovy title="build.gradle"
dependencies {
    implementation 'com.lacunasoftware.cloudhub:cloudhub-client:1.0.5'
}
```

Com Maven:

```xml
<dependency>
    <groupId>com.lacunasoftware.cloudhub</groupId>
    <artifactId>cloudhub-client</artifactId>
    <version>1.0.5</version>
</dependency>
```

## 3. Criar um cliente

**C#**

```csharp
using Lacuna.Cloudhub.Api;
using Lacuna.Cloudhub.Client;

var cloudhub = new CloudhubClient(
    "https://cloudhub.lacunasoftware.com/",
    Environment.GetEnvironmentVariable("CLOUDHUB_API_KEY"));
```

Com o `Lacuna.Cloudhub.ClientCore`, registre o cliente uma vez e injete `ICloudhubService`, que tem
os mesmos métodos:

```csharp title="Program.cs"
using Lacuna.Cloudhub.ClientCore;

builder.Services.AddCloudhub().Configure(builder.Configuration.GetSection("Cloudhub"));
```

```json title="appsettings.json"
{
  "Cloudhub": {
    "Endpoint": "https://cloudhub.lacunasoftware.com/",
    "ApiKey": "set it in user secrets or an environment variable"
  }
}
```

**PHP**

```php
<?php
require __DIR__ . '/vendor/autoload.php';

use Lacuna\Cloudhub\CloudhubClient;

// The client appends "api/..." to the address, so keep the trailing slash
$cloudhub = new CloudhubClient('https://cloudhub.lacunasoftware.com/', getenv('CLOUDHUB_API_KEY'));
```

Os métodos do cliente PHP terminam em `Async`, mas são síncronos: cada um devolve o resultado.

**Python**

```python title="cloudhub.py"
import os

import requests

CLOUDHUB_URL = 'https://cloudhub.lacunasoftware.com'

_http = requests.Session()
_http.headers['X-Api-Key'] = os.environ['CLOUDHUB_API_KEY']


class CloudHubError(Exception):
    """An error CloudHub reported, such as SessionExpired."""

    def __init__(self, code, message):
        super().__init__(f'{code}: {message}')
        self.code = code


def call_cloudhub(method, path, **kwargs):
    # A signature can wait on the provider, so allow a generous timeout
    response = _http.request(method, CLOUDHUB_URL + path, timeout=120, **kwargs)
    if response.status_code == 422:
        error = response.json()
        raise CloudHubError(error.get('code'), error.get('message'))
    response.raise_for_status()
    return response.json()
```

**Node.js**

```js
const { CloudHubClient, TrustServiceSessionTypes } = require('cloudhub-client');

const cloudhub = new CloudHubClient('https://cloudhub.lacunasoftware.com/', process.env.CLOUDHUB_API_KEY);
```

**Java**

```java
import cloudhub.SessionsApi;
import cloudhub.client.CloudhubClient;

// No trailing slash: the client appends "/api/..." to the address
SessionsApi cloudhub = new SessionsApi(
    new CloudhubClient("https://cloudhub.lacunasoftware.com", System.getenv("CLOUDHUB_API_KEY")));
```

Um `CloudhubClient` guarda estado de cada requisição, então não compartilhe uma instância entre
threads que a usem ao mesmo tempo.

## 4. Criar uma sessão

Envie o CPF do signatário, só com os dígitos, e a URI para onde o CloudHub deve devolver o usuário.
Os exemplos levam o ID do arquivo a assinar na URI de redirecionamento, para que a requisição que
volta saiba de qual arquivo se trata.

**C#**

```csharp
var session = await cloudhub.CreateSessionAsync(new SessionCreateRequest {
    Identifier = "12345678909",
    RedirectUri = "https://app.example.com/sign/complete?fileId=42",
    Type = TrustServiceSessionTypes.SingleSignature,
    LifetimeInSeconds = 300,
});

foreach (var service in session.Services) {
    Console.WriteLine($"{service.ServiceInfo.Provider}: {service.AuthUrl}");
}
```

**PHP**

```php
use Lacuna\Cloudhub\SessionCreateRequest;
use Lacuna\Cloudhub\TrustServiceSessionTypes;

$session = $cloudhub->createSessionAsync(new SessionCreateRequest(
    '12345678909',                                      // the signer's CPF
    'https://app.example.com/sign/complete?fileId=42',  // redirect URI
    TrustServiceSessionTypes::SingleSignature,
    300                                                 // lifetime in seconds; 300 if omitted
));

foreach ($session->services as $service) {
    echo $service->serviceInfo->provider . ': ' . $service->authUrl . PHP_EOL;
}
```

**Python**

```python
from cloudhub import call_cloudhub

session = call_cloudhub('POST', '/api/sessions', json={
    'identifier': '12345678909',
    'redirectUri': 'https://app.example.com/sign/complete?fileId=42',
    'type': 'SingleSignature',
    'lifetimeInSeconds': 300,
})

for service in session['services']:
    print(service['serviceInfo']['provider'], service['authUrl'])
```

**Node.js**

```js
const session = await cloudhub.createSessionAsync({
  identifier: '12345678909',
  redirectUri: 'https://app.example.com/sign/complete?fileId=42',
  type: TrustServiceSessionTypes.SingleSignature,
  lifetimeInSeconds: 300,
});

for (const service of session.services || []) {
  console.log(`${service.serviceInfo.provider}: ${service.authUrl}`);
}
```

**Java**

```java
import cloudhub.client.model.SessionCreateRequest;
import cloudhub.client.model.SessionModel;
import cloudhub.client.model.TrustServiceAuthParametersModel;
import cloudhub.client.model.TrustServiceSessionTypes;

SessionCreateRequest request = new SessionCreateRequest();
request.setIdentifier("12345678909");
request.setRedirectUri("https://app.example.com/sign/complete?fileId=42");
request.setType(TrustServiceSessionTypes.SINGLE_SIGNATURE);
request.setLifetimeInSeconds(300);

SessionModel session = cloudhub.apiSessionsPost(request);
for (TrustServiceAuthParametersModel service : session.getServices()) {
    System.out.println(service.getServiceInfo().getProvider() + ": " + service.getAuthUrl());
}
```

Uma lista vazia significa que nenhum provedor tem um certificado para esse CPF. Informe o usuário e
ofereça a opção de tentar outro CPF.

## 5. Enviar o usuário a um provedor

Mostre uma opção para cada entrada e leve o navegador para a `authUrl` que o usuário escolher.
`badgeUrl` é o logotipo do provedor e pode ser nula.

```html
<!-- One link per entry of "services" -->
<a href="https://api.birdid.com.br/oauth/authorize?client_id=…">
  <img src="https://cdn.lacunasoftware.com/img/psc/birdid.png" alt="BirdID" height="48">
  Sign with SOLUTI
</a>
```

O usuário entra no provedor e aprova. O provedor devolve o navegador ao CloudHub, e o CloudHub o
envia à sua URI de redirecionamento.

## 6. Ler o certificado quando o usuário voltar

A sua URI de redirecionamento recebe o parâmetro `session` ao lado dos seus. Quando `session` não
vem, o usuário não autorizou: leve-o de volta para escolher de novo.

**C#**

```csharp
// ASP.NET Core: GET /sign/complete?fileId=42&session=…
public async Task<IActionResult> Complete(string fileId, string session) {
    if (string.IsNullOrEmpty(session)) {
        return RedirectToAction("Index", new { fileId });
    }

    byte[] certificate = await cloudhub.GetCertificateAsync(session);
    // ...
}
```

**PHP**

```php
// complete.php?fileId=42&session=…
$fileId = $_GET['fileId'];
$session = $_GET['session'] ?? null;
if (empty($session)) {
    header('Location: /sign.php?fileId=' . urlencode($fileId));
    exit;
}

$certificate = $cloudhub->getCertificateAsync($session); // base64
```

**Python**

```python
from flask import redirect, request, url_for

@app.route('/sign/complete')
def complete():
    file_id = request.args['fileId']
    session = request.args.get('session')
    if not session:
        return redirect(url_for('sign', fileId=file_id))

    certificate = call_cloudhub('GET', '/api/sessions/certificate',
                                params={'session': session})  # base64
    # ...
```

**Node.js**

```js
router.get('/sign/complete', async (req, res, next) => {
  try {
    const { fileId, session } = req.query;
    if (!session) {
      return res.redirect(`/sign?fileId=${encodeURIComponent(fileId)}`);
    }

    const certificate = await cloudhub.getCertificateAsync(session); // base64
    // ...
  } catch (err) {
    next(err);
  }
});
```

**Java**

```java
import cloudhub.client.ApiException;
import java.nio.charset.StandardCharsets;

@GetMapping("/sign/complete")
public String complete(@RequestParam String fileId,
                       @RequestParam(required = false) String session) throws ApiException {
    if (session == null || session.isEmpty()) {
        return "redirect:/sign?fileId=" + fileId;
    }

    String certificate = unquote(cloudhub.apiSessionsCertificateGet(session)); // base64
    // ...
}

// The certificate and sign-hash operations answer with a JSON string ("MIIG..."),
// and the client hands back its raw bytes, quotes included
private static String unquote(byte[] body) {
    return new String(body, StandardCharsets.UTF_8).replace("\"", "");
}
```

Antes de assinar, confira se o certificado pertence à pessoa esperada: compare o CPF dele com o que
você enviou. Os exemplos completos fazem isso com a biblioteca de assinatura.

## 7. Assinar um hash

A sua biblioteca de assinatura fornece o hash a assinar e o algoritmo de hash dele. Envie os dois com
a sessão; o resultado é o valor da assinatura, que a biblioteca insere no documento. Identifique o
algoritmo pelo OID (`digestAlgorithmOid`), como as bibliotecas de assinatura o informam, ou pelo nome
(`digestAlgorithm`, como `SHA-256`).

**C#**

```csharp
byte[] signature = await cloudhub.SignHashAsync(new SignHashRequest {
    Session = session,
    Hash = toSignHash,                  // the hash, as bytes
    DigestAlgorithmOid = digestAlgorithmOid,
});
```

**PHP**

```php
use Lacuna\Cloudhub\SignHashRequest;

$signature = $cloudhub->signHashAsync(new SignHashRequest(
    $session,
    $toSignHashBase64,      // the hash, base64-encoded
    null,                   // the digest algorithm name...
    $digestAlgorithmOid     // ...or its OID
)); // base64
```

**Python**

```python
signature = call_cloudhub('POST', '/api/sessions/sign-hash', json={
    'session': session,
    'hash': to_sign_hash_base64,
    'digestAlgorithmOid': digest_algorithm_oid,
})  # base64
```

**Node.js**

```js
const signature = await cloudhub.signHashAsync({
  session,
  hash: toSignHashBase64,
  digestAlgorithmOid,
}); // base64
```

**Java**

```java
import cloudhub.client.model.SignHashRequest;
import java.util.Base64;

SignHashRequest signRequest = new SignHashRequest();
signRequest.setSession(session);
signRequest.setHash(toSignHash);                  // the hash, as bytes
signRequest.setDigestAlgorithmOid(digestAlgorithmOid);

byte[] signature = Base64.getDecoder().decode(unquote(cloudhub.apiSessionsSignHashPost(signRequest)));
```

## 8. Tratar erros

O CloudHub informa um problema na sua requisição com HTTP 422 e um corpo JSON com `code`, `message`
e, às vezes, `details`. O código mais comum é `SessionExpired`: a sessão expirou antes de ser usada,
então envie o usuário ao provedor de novo. A página [API REST](https://docs.lacunasoftware.com/articles/cloudhub/rest-api#erros) lista todos os
códigos.

**C#**

```csharp
try {
    var signature = await cloudhub.SignHashAsync(request);
} catch (CloudhubApiException ex) when (ex.CodeStr == "SessionExpired") {
    // Create a new session and send the user to the provider again
}
```

Compare `CodeStr`. A propriedade `Code`, do tipo enum, não é preenchida a partir da resposta.

**PHP**

```php
use GuzzleHttp\Exception\ClientException;

try {
    $signature = $cloudhub->signHashAsync($request);
} catch (ClientException $e) {
    $error = json_decode((string) $e->getResponse()->getBody(), true);
    if (($error['code'] ?? null) === 'SessionExpired') {
        // Create a new session and send the user to the provider again
    }
}
```

**Python**

```python
from cloudhub import CloudHubError

try:
    signature = call_cloudhub('POST', '/api/sessions/sign-hash', json=sign_request)
except CloudHubError as e:
    if e.code == 'SessionExpired':
        pass  # Create a new session and send the user to the provider again
```

**Node.js**

```js
try {
  const signature = await cloudhub.signHashAsync(request);
} catch (err) {
  // cloudhub-client 1.x throws a string: CloudHub's JSON error followed by the HTTP message
  if (String(err).includes('"SessionExpired"')) {
    // Create a new session and send the user to the provider again
  }
}
```

**Java**

```java
try {
    byte[] signature = cloudhub.apiSessionsSignHashPost(request);
} catch (ApiException e) {
    if (e.getCode() == 422 && e.getResponseBody().contains("\"SessionExpired\"")) {
        // Create a new session and send the user to the provider again
    }
}
```

Uma requisição sem uma chave de API válida recebe HTTP 401. Uma falha inesperada, como um provedor
fora do ar, recebe HTTP 500 com um código curto no corpo; informe-o ao falar com o suporte.

## Próximos passos

- [Assinando um PDF](https://docs.lacunasoftware.com/articles/cloudhub/pades-signature): o fluxo PAdES completo, com a biblioteca de assinatura
  calculando o hash e finalizando o PDF.
- [Autenticação](https://docs.lacunasoftware.com/articles/cloudhub/authentication): permita que os usuários entrem com o certificado em nuvem.
- [Como funciona](https://docs.lacunasoftware.com/articles/cloudhub/how-it-works#tipos-de-sessão): `SignatureSession` para assinar vários documentos
  com uma única autorização.
