Webhook

¿Qué es un webhook?

Un webhook es un conjunto de endpoints REST que usted, cliente de Sensedia, deberá proveer. Cuando haya una solicitud de credenciales por parte de un desarrollador, el Developer Portal llamará a este conjunto de endpoints REST. Así, el desarrollador podrá acceder a sus APIs protegidas en el Developer Portal.

Aquí sigue un resumen del flujo de comunicación entre Developer Portal y webhook durante la solicitud de credenciales:

  1. Desarrollador se registra en el Developer Portal.

  2. Desarrollador va al menú Apps y crea una nueva app AWS (apps del API Manager Sensedia no utilizan webhook).

  3. Sistema Developer Portal hace una solicitud HTTP al webhook, pasando la información de la app AWS del desarrollador.

  4. Webhook, en su infraestructura, recibe la solicitud, crea las credenciales y las devuelve en la misma solicitud HTTP.

  5. Sistema Developer Portal recibe la respuesta, guarda las credenciales en la base de datos y proporciona acceso al desarrollador.

Especificación

Es su responsabilidad implementar el webhook en su infraestructura, pero puede consultar un ejemplo de implementación.
La única obligación es seguir los contratos de los endpoints REST para realizar la integración entre el Developer Portal y el webhook.

Especificación de Open API

Para crear el webhook, deberá implementar este Contrato de Open API.

La especificación ha sido modificada. Se ha agregado el campo customCredentials (opcional), que puede utilizarse con API KEY y CLIENT CREDENTIALS.

Endpoints

A continuación, vea los endpoints que deberá implementar y los detalles de solicitud y respuesta de cada uno de ellos.

Acción Endpoint

Crear credenciales

POST /v1/createCredentials

Actualizar credenciales

POST /v1/updateCredentials

Revocar credenciales

POST /v1/revokeCredentials

Verificar disponibilidad

GET /v1/health

Autenticación

Todos los endpoints utilizarán autenticación básica.
Recibirá el header Authorization: Basic <username:password base64> y deberá validarlo de la forma que desee.

Crear credenciales

POST /v1/createCredentials

Endpoint responsable de crear y devolver las credenciales de la 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
    }

La respuesta puede devolver tanto una API Key como Client Credentials.

Para el credentialType=API_KEY, se deben devolver los campos apiKeyId y apiKey.

Para el credentialType=CLIENT_CREDENTIALS, se deben devolver los campos clientId y clientSecret.

La especificación de CreateCredentialsResponse ha sido modificada. Se ha incluido el campo customCredentials (opcional), para devolver información adicional, como API KEY y CLIENT CREDENTIALS.

Actualizar credenciales

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: vacío

La especificación de UpdateCredentialsRequest ha sido modificada. Se ha incluido el campo customCredentials (opcional), para devolver información adicional, como API KEY y CLIENT CREDENTIALS.

Revocar credenciales

POST /v1/revokeCredentials

Endpoint responsable de revocar (efectivamente desactivar o eliminar) credenciales de la 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: vacío

La especificación de RevokeCredentialsRequest ha sido modificada. Se ha incluido el campo customCredentials (opcional), para devolver información adicional, como API KEY y CLIENT CREDENTIALS.

Verificar disponibilidad

GET /v1/health

Endpoint de gestión de la aplicación.
Debe retornar 204 No Content si la solicitud se realiza con éxito.
Puede retornar otros códigos de estado como 401, 500 etc.

Request

  • Header:

    Authorization: Basic <username:password base64>
  • Body: vacío

Response

  • Status code: 204 No Content

  • Body: vacío

Casos de error

Si el webhook devuelve un error (código de estado 4xx o 5xx), el formato esperado del mensaje es:

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

Ejemplo de implementación del webhook

AWS Lambda

A continuación, vea un ejemplo de una Lambda AWS, en python, que implementa todos los endpoints.

El código a continuación es solo una referencia. Debe modificarlo según sus necesidades de seguridad o reglas de negocio.
La única obligación es seguir el contrato definido en la especificación de Open API.

Para descargar el modelo, haga clic aquí.

Creando credenciales

Para crear credenciales, hay dos métodos:

API Keys

Las API Keys se generan con el nombre de la app del Developer Portal y el correo electrónico del desarrollador que creó la app.

Vea el ejemplo a continuación:

 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))

Este método debe generar una nueva clave en la consola de AWS:

aws new key console

Y asociarla a los Usage Plans:

usage plan

Client Credentials

Las Client Credentials se configuran por el App Clients de un Cognito User Pool.

Vea el ejemplo a continuación:

 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))

Este método deberá crear un nuevo App Client:

client credentials app client

Este app client tendrá las configuraciones necesarias para generar Client Credentials:

app client config

Para autenticar las APIs usando tokens JWT, usted debe tener el autorizador configurado en el Gateway de AWS y vinculado al pool de usuarios de Cognito.

edit authorizer
Para que los desarrolladores puedan generar los tokens, necesitará proporcionar un endpoint que entregue el client ID y el client secret generados durante la creación de la aplicación.

En el ejemplo a continuación, se utiliza un endpoint de Cognito que genera el token según el 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"
}

Revocando credenciales

API Keys

Vea el ejemplo a continuación:

 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 eliminará la API Key de la consola de AWS.

Client Credentials

Vea el ejemplo a continuación:

 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 eliminará el app client de la consola de AWS.

Verificando la disponibilidad

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]