Webhook

O que é um webhook?

Um webhook é um conjunto de endpoints REST que você, cliente da Sensedia, deverá providenciar. Quando houver uma solicitação de credenciais por parte de um desenvolvedor, o Developer Portal irá chamar esse conjunto de endpoints REST. Assim, o desenvolvedor poderá acessar suas APIs protegidas no Developer Portal.

Segue abaixo um resumo do fluxo de comunicação entre Developer Portal e webhook durante a solicitação de credenciais:

  1. Desenvolvedor se cadastra no Developer Portal.

  2. Desenvolvedor vai no menu Apps e cria um novo app AWS (apps do API Manager Sensedia não utilizam webhook).

  3. Sistema Developer Portal faz uma requisição HTTP ao webhook, passando as informações do app AWS do desenvolvedor.

  4. Webhook, na sua infraestrutura, recebe a requisição, cria as credenciais e retorna na mesma requisição HTTP.

  5. Sistema Developer Portal recebe o retorno, grava as credenciais no banco de dados e disponibiliza acesso para o desenvolvedor.

Especificação

É de sua responsabilidade implementar o webhook na sua infraestrutura, mas você pode consultar um exemplo de implementação.
A única obrigatoriedade é seguir os contratos dos endpoints REST para realizar a integração entre Developer Portal e webhook.

Open API Specification

Para criar o webhook, você deverá implementar este Contrato Open API.

A especificação foi alterada. Foi adicionado o campo customCredentials (opcional), que pode ser udado com API KEY e CLIENT CREDENTIALS.

Endpoints

Veja abaixo os endpoints que você deverá implementar e os detalhes de requisição e resposta de cada um deles.

Ação Endpoint

Criar credenciais

POST /v1/createCredentials

Atualizar credenciais

POST /v1/updateCredentials

Revogar credenciais

POST /v1/revokeCredentials

Verificar disponibilidade

GET /v1/health

Autenticação

Todos os endpoints utilizarão basic authentication.
Você receberá o header Authorization: Basic <username:password base64> e deverá validá-lo da forma que desejar.

Criar credenciais

POST /v1/createCredentials

Endpoint responsável por criar e retornar as credenciais do app.

Request

  • Header:

    Authorization: Basic <username:password base64>
  • Body:

    {
     "appName": "string",
     "developer": "string",
     "apis": [
       {
         "id": "string",
         "usagePlans": [
           {
             "id": "string"
           }
         ]
       }
     ]
    }

Response

  • Status code: 200 OK

  • Body:

    {
     "credentialType": "CLIENT_CREDENTIALS | API_KEY",
     "clientId": string,
     "clientSecret": string,
     "apiKeyId": string,
     "apiKey": string
    }

A resposta pode retornar tanto uma API Key quanto Client Credentials.

Para o credentialType=API_KEY, devem ser retornados os campos apiKeyId e apiKey.

Para o credentialType=CLIENT_CREDENTIALS, devem ser retornados os campos clientId e clientSecret.

A especificação de CreateCredentialsResponse foi alterada. Foi incluído o campo customCredentials (opcional), para retorno de informações adicionais, como API KEY e CLIENT CREDENTIALS.

Atualizar credenciais

POST /v1/updateCredentials

Request

  • Header:

    Authorization: Basic <username:password base64>
  • Body:

    {
     "updatedAt": "2024-03-01T18:58:47.878561013Z[GMT]",
     "appName": "string",
     "developer": "string",
     "credentialType": "CLIENT_CREDENTIALS | API_KEY",
     "clientId": "string",
     "apiKeyId": "string",
     "apis": [
       {
         "id": "58baecsdd4",
         "action": "NONE | ADDED | REMOVED",
         "usagePlans": [
           {
             "id": "3llq7e",
             "action": "NONE | ADDED | REMOVED"
           }
         ]
       }
     ]
    }

Response

  • HTTP status: 204 No Content

  • Body: vazio

A especificação de UpdateCredentialsRequest foi alterada. Foi incluído o campo customCredentials (opcional), para retorno de informações adicionais, como API KEY e CLIENT CREDENTIALS.

Revogar credenciais

POST /v1/revokeCredentials

Endpoint responsável por revogar (efetivamente desativar ou excluir) credenciais do app.

Request

  • Header:

    Authorization: Basic <username:password base64>
  • Body:

    {
     "appName": "string",
     "developer": "string",
     "credentialType": "CLIENT_CREDENTIALS | API_KEY",
     "clientId": string,
     "clientSecret": string,
     "apiKeyId": string,
     "apiKey": string
    }

Response

  • Status code: 204 No Content

  • Body: vazio

A especificação de RevokeCredentialsRequest foi alterada. Foi incluído o campo customCredentials (opcional), para retorno de informações adicionais, como API KEY e CLIENT CREDENTIALS.

Verificar disponibilidade

GET /v1/health

Endpoint de gerenciamento da aplicação.
Deve retornar 204 No Content caso a requisição seja realizada com sucesso.
Pode retornar outros status codes como 401,500 etc.

Request

  • Header:

    Authorization: Basic <username:password base64>
  • Body: vazio

Response

  • Status code: 204 No Content

  • Body: vazio

Casos de erro

Caso o webhook retorne algum erro (status code 4xx ou 5xx), o formato esperado da mensagem é:

{
   "timestamp": date-time,
   "status": integer,
   "error": string,
   "messages": [
       string
   ],
   "path": string
}

Exemplo de implementação do webhook

AWS Lambda

Veja abaixo um exemplo de uma Lambda AWS, em python, que implementa todos os endpoints.

O código abaixo é apenas uma referência.
Você deve alterá-lo conforme suas necessidades de segurança ou regras de negócio.
A única obrigatoriedade é seguir o contrato definido na especificação Open API.

Para fazer o download do modelo, clique aqui.

Criando credenciais

Para criar credenciais, há dois métodos:

API Keys

As API Keys são geradas com o nome do app do Developer Portal e o e-mail do desenvolvedor que criou o app.

Veja o exemplo abaixo:

 def create_api_key(request_body):
    """Creates a new API Key and associates it with the requested Usage Plans. Return the API Key ID and value."""
    client = boto3.client("apigateway")

    app_slug = request_body.get("appSlug")
    developer = request_body.get("developer")
    apis = request_body.get("apis", [])

    try:
        api_key_response = client.create_api_key(
            name=create_credential_name(app_slug, developer),
            enabled=True,
            # tags={'string':'string'} can be used to distinct API Key created by the webhook
        )

        api_key_id = api_key_response["id"]
        api_key_value = api_key_response["value"]

        # Iterate over APIs in the request body
        for api_data in apis:
            # api_id = api_data.get('id') can be used to verify if API is still related to the Usage Plan
            usage_plans = api_data.get("usagePlans", [])

            # Iterate over usage plans for each API
            for usage_plan in usage_plans:
                usage_plan_id = usage_plan.get("id")

                # Associate API Key with Usage Plan
                client.create_usage_plan_key(
                    usagePlanId=usage_plan_id, keyId=api_key_id, keyType="API_KEY"
                )

        return {
            "statusCode": 200,
            "body": json.dumps(
                {
                    "credentialType": "API_KEY",
                    "apiKeyId": api_key_id,
                    "apiKey": api_key_value,
                    "customCredentials": {
                        "qaautomation-token-1": "qaautomationtoken1",
                        "qaautomation-token-2": "qaautomationtoken2",
                        "qaautomation-token-3": "qaautomationtoken3",
                        "qaautomation-token-4": "qaautomationtoken4",
                        "qaautomation-token-5": "qaautomationtoken5"
                    },

                }
            ),
        }
    except Exception as e:
        logger.error("create_api_key error: %s", str(e))
        return handle_error(str(e))

Esse método deve gerar uma nova chave no console da AWS:

aws new key console

E associá-la ao Usage Plans:

usage plan

Client Credentials

As Client Credentials são configuradas pelo App Clients de um Cognito User Pool.

Veja o exemplo abaixo:

 def create_cognito_app_client(request_body):
    """Creates a Cognito app client and returns the client id and client secret."""
    cognito_client = boto3.client("cognito-idp")
    app_slug = request_body.get("appSlug")
    developer = request_body.get("developer")

    try:
        response = cognito_client.create_user_pool_client(
            UserPoolId=USER_POOL_ID,
            ClientName=create_credential_name(app_slug, developer),
            GenerateSecret=True,
            AllowedOAuthFlowsUserPoolClient=True,
            AllowedOAuthScopes=OAUTH_CUSTOM_SCOPES,
            AllowedOAuthFlows=["client_credentials"],
            SupportedIdentityProviders=["COGNITO"],
        )

        app_client_id = response["UserPoolClient"]["ClientId"]
        app_client_secret = response["UserPoolClient"]["ClientSecret"]

        return {
            "statusCode": 200,
            "body": json.dumps(
                {
                    "credentialType": "CLIENT_CREDENTIALS",
                    "clientId": app_client_id,
                    "clientSecret": app_client_secret,
                }
            ),
        }
    except Exception as e:
        logger.error("create_user_pool_client error: %s", str(e))
        return handle_error(str(e))

Esse método deverá criar um novo App Client:

client credentials app client

Este app client terá as configurações necessárias para gerar Client Credentials:

app client config

Para autenticar as APIs usando tokens JWT, você deve ter o autorizador configurado no Gateway da AWS e vinculado ao pool de usuários do Cognito.

edit authorizer
Para que os desenvolvedores possam gerar os tokens, você precisará disponibilizar um endpoint fornecendo o client ID e o client secret gerados durante a criação do aplicativo.

No exemplo abaixo, é utilizado um endpoint do Cognito que gera o token de acordo com o grant-type:

Request

curl --location 'https://<your-cognito-domain>.auth.us-east-1.amazoncognito.com/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic MmhjZnVuN3Y4amRvYXA4NHNlNjQ0bWZxdm06dXQxcnVrZWs4amU3YXZiNjU4dGZwdTFwY2hrcDFpYzE2azhkbWJzYnJvcGl2amk4cWln' \
--data-urlencode 'grant_type=client_credentials'

Response

{
   "access_token": "eyJraWQiOiJnSlV3UTFOT1ZVbmVHRWJaWGNQK1J2TGdzaGNOOTM1MHZwUVJwbWRnXC9hYz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIyaGNmdW43djhqZG9hcDg0c2U2NDRtZnF2bSIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoid2ViaG9vay1kZW1vLWlkZW50aWZpZXJcL2pzb24ucmVhZCIsImF1dGhfdGltZSI6MTcwOTMyMDk0NCwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfZVA2aVZ5UDhaIiwiZXhwIjoxNzA5MzI0NTQ0LCJpYXQiOjE3MDkzMjA5NDQsInZlcnNpb24iOjIsImp0aSI6IjFiZjcyMGQ0LTQ3NjUtNGU1Zi1hNDU1LTg4NDg0YTQ5MzFlNyIsImNsaWVudF9pZCI6IjJoY2Z1bjd2OGpkb2FwODRzZTY0NG1mcXZtIn0.MQlT8YXAkXEmtLiFV_K7pq6aEylwEo1FrAx1At3PeFHkZ82lbuxXcHvVU8CFUfhURAXBqhTRR-KTT1LpBj6i_JUUhr_2obwVgZfKn1n94pfRw0tny8S5g88vuhuqNXn4CypnwrtGzoyYgV9liXykMX-80Y6ILZgtBcaFaBwpEsShv9Q9Q1S-XwQcQSCG0NF4-LF-bFx_KW3ZA3kV5IVVP0XpupYiP7My346kQUqCYboEeaoTDbEz_HqPTI6-r9Wnud7FvrXzl6YT0fhU6SHhx5QeI-zARemrI561Xf5cKkljuYSOWkSBRTADV-pKMj--X6WiBRZmDPN2f-0ziwAQpw",
   "expires_in": 3600,
   "token_type": "Bearer"
}

Revogando credenciais

API Keys

Veja o exemplo abaixo:

 def delete_api_key(request_body):
    """Deletes an API key."""

    client = boto3.client("apigateway")

    try:
        current_credential_type = request_body.get("credentialType")
        validate_credential_type_is_supported(current_credential_type)

        api_key_id = request_body.get("apiKeyId")
        if api_key_id is not None:
            client.delete_api_key(apiKey=api_key_id)
    except client.exceptions.NotFoundException:
        pass
    except Exception as e:
        logger.error("delete_api_key error: %s", str(e))
        return handle_error(str(e))

    return {"statusCode": 204}

Este método irá excluir a API Key do console da AWS.

Client Credentials

Veja o exemplo abaixo:

 def delete_cognito_app_client(request_body):
    """Deletes a Cognito app client."""
    cognito_client = boto3.client("cognito-idp")

    try:
        current_credential_type = request_body.get("credentialType")
        validate_credential_type_is_supported(current_credential_type)

        app_client_id = request_body.get("clientId")
        cognito_client.delete_user_pool_client(
            UserPoolId=USER_POOL_ID, ClientId=app_client_id
        )
    except cognito_client.exceptions.ResourceNotFoundException:
        pass
    except Exception as e:
        logger.error("delete_user_pool_client error: %s", str(e))
        return handle_error(str(e))

    return {"statusCode": 204}

Este método irá excluir o app client da console da AWS.

Verificando disponibilidade

def check_health():
    """Checks the health of the service."""
    return {"statusCode": 204}
Thanks for your feedback!
EDIT

Share your suggestions with us!
Click here and then [+ Submit idea]