Integration Amazon Web Services
Architecture de reference pour integrer Certyo avec AWS en utilisant EventBridge, Lambda, Step Functions et API Gateway.
Prerequis
- Compte AWS avec des permissions IAM pour EventBridge, Lambda, Step Functions et Secrets Manager
- AWS CLI v2 configure avec des credentials
- Cle API Certyo (stockee dans AWS Secrets Manager — voir la section Authentification ci-dessous)
- Node.js 20+ ou Python 3.12+ (pour le developpement Lambda)
Architecture de reference
Cette architecture exploite le routage d'evenements natif AWS et le calcul serverless pour s'integrer avec Certyo :
Source System ──► EventBridge ──► API Destination (Certyo) ──► POST /api/v1/records
│ │
DLQ (SQS) Step Functions
for failed (verification workflow)
deliveries │
┌──────┴──────┐
│ Wait 90s │
│ Verify │
│ Branch │
└──────┬──────┘
│
SNS notification
(anchored / failed)EventBridge recoit les evenements de domaine de vos systemes sources (S3, DynamoDB Streams, applications personnalisees). Pour le transfert simple, API Destinations appelle Certyo directement sans intermediaire Lambda. Pour les transformations complexes, Lambda traite les evenements avant l'ingestion. Step Functions orchestrent les workflows de verification multi-etapes.
EventBridge API Destinations
Le modele d'integration le plus simple : EventBridge appelle directement l'API Certyo via une API Destination. Aucun code Lambda requis pour le transfert direct d'evenements.
Comment ca fonctionne
- Connection — Stocke la cle API Certyo avec le type d'authentification
API_KEY. EventBridge gere la rotation des credentials si vous mettez a jour le secret. - API Destination — Definit l'URL de l'endpoint Certyo, la methode HTTP et la limite de debit d'invocation.
- Rule — Correspond aux evenements entrants par source, detail-type ou modeles personnalises et les route vers l'API Destination.
- Input Transformer — Mappe votre schema d'evenement vers le payload d'enregistrement Certyo sans code Lambda.
- Retry + DLQ — Reessai integre avec backoff exponentiel (jusqu'a 24 heures avec 185 reessais). Les evenements echoues arrivent dans une file SQS de dead-letter.
Lambda Functions
Pour les evenements necessitant une transformation, un enrichissement ou une logique conditionnelle avant l'ingestion, utilisez Lambda comme couche de traitement :
- Declencheur S3 — De nouveaux objets dans un bucket declenchent une Lambda qui lit le fichier, extrait les enregistrements et envoie chacun a Certyo
- DynamoDB Streams — Capturez les donnees de changement des tables DynamoDB et transmettez les elements modifies comme enregistrements Certyo
- Gestionnaire de Webhook — Recevez des webhooks de systemes tiers via API Gateway, transformez et ingerez
Step Functions
Orchestrez des workflows multi-etapes qui ingerent un enregistrement, attendent l'ancrage, verifient le resultat et bifurquent en succes ou echec :
- Standard Workflow — Pour les flux de verification longue duree (jusqu'a 1 an d'execution). Utilise des etats Wait pour faire une pause pendant la fenetre d'ancrage de ~60-90 secondes de Certyo.
- Express Workflow — Pour les executions a haut volume et courte duree (jusqu'a 5 minutes). Ideal pour l'ingestion en masse ou la verification est geree separement.
API Gateway
Placez Certyo devant les consommateurs internes avec API Gateway pour les plans d'utilisation, la limitation et la gestion des cles API :
- Creez un proxy REST API qui transmet les requetes a
https://www.certyos.com/api/v1/ - Ajoutez un plan d'utilisation avec limitation (par exemple, 100 requetes/seconde par consommateur) et des quotas mensuels
- Injectez le
X-API-KeyCertyo via un autoriseur Lambda ou un template de mapping VTL - Les consommateurs internes s'authentifient avec leurs propres cles API Gateway — ils ne voient jamais la cle Certyo
Exemples de code
import json
import os
import urllib.parse
import boto3
import urllib3
secrets_client = boto3.client("secretsmanager")
s3_client = boto3.client("s3")
http = urllib3.PoolManager()
CERTYO_API_URL = os.environ.get("CERTYO_API_URL", "https://www.certyos.com")
CERTYO_TENANT_ID = os.environ["CERTYO_TENANT_ID"]
SECRET_ARN = os.environ["CERTYO_SECRET_ARN"]
_api_key_cache = None
def get_api_key():
global _api_key_cache
if _api_key_cache is None:
secret = secrets_client.get_secret_value(SecretId=SECRET_ARN)
_api_key_cache = json.loads(secret["SecretString"])["apiKey"]
return _api_key_cache
def handler(event, context):
"""
Triggered by S3 PutObject events. Reads the uploaded JSON file,
extracts records, and sends each to Certyo for blockchain anchoring.
"""
api_key = get_api_key()
results = []
for record in event["Records"]:
bucket = record["s3"]["bucket"]["name"]
key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])
print(f"Processing s3://{bucket}/{key}")
# Read the S3 object
obj = s3_client.get_object(Bucket=bucket, Key=key)
body = json.loads(obj["Body"].read().decode("utf-8"))
# Support single record or array of records
items = body if isinstance(body, list) else [body]
for item in items:
record_id = item.get("id", key.split("/")[-1].replace(".json", ""))
payload = {
"tenantId": CERTYO_TENANT_ID,
"database": f"s3-{bucket}",
"collection": key.rsplit("/", 1)[0] if "/" in key else "root",
"recordId": record_id,
"recordVersion": str(item.get("version", "1")),
"operationType": "upsert",
"recordPayload": item,
"sourceTimestamp": record["eventTime"],
"idempotencyKey": f"{record_id}-{record['eventTime'][:10]}",
}
response = http.request(
"POST",
f"{CERTYO_API_URL}/api/v1/records",
body=json.dumps(payload),
headers={
"X-API-Key": api_key,
"Content-Type": "application/json",
},
)
result = json.loads(response.data.decode("utf-8"))
if response.status != 202:
print(f"ERROR: Certyo returned {response.status} for {record_id}: {result}")
raise Exception(f"Ingestion failed for {record_id}: {response.status}")
print(f"Record {record_id} accepted. Hash: {result['recordHash']}")
results.append(result)
return {
"statusCode": 200,
"body": json.dumps({
"message": f"Ingested {len(results)} records",
"records": results,
}),
}import * as cdk from "aws-cdk-lib";
import * as events from "aws-cdk-lib/aws-events";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as sqs from "aws-cdk-lib/aws-sqs";
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
import { Construct } from "constructs";
export class CertyoEventBridgeStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Store the Certyo API key in Secrets Manager
const certyoSecret = new secretsmanager.Secret(this, "CertyoApiKey", {
secretName: "certyo/api-key",
description: "Certyo API key for record ingestion",
secretObjectValue: {
apiKey: cdk.SecretValue.unsafePlainText("REPLACE_WITH_ACTUAL_KEY"),
},
});
// Dead-letter queue for failed deliveries
const dlq = new sqs.Queue(this, "CertyoDlq", {
queueName: "certyo-eventbridge-dlq",
retentionPeriod: cdk.Duration.days(14),
encryption: sqs.QueueEncryption.SQS_MANAGED,
});
// EventBridge Connection — stores Certyo API key credentials
const connection = new events.Connection(this, "CertyoConnection", {
connectionName: "certyo-api-connection",
description: "Connection to Certyo Records API",
authorization: events.Authorization.apiKey(
"X-API-Key",
cdk.SecretValue.secretsManager(certyoSecret.secretArn, {
jsonField: "apiKey",
})
),
});
// EventBridge API Destination — the Certyo endpoint
const apiDestination = new events.ApiDestination(
this,
"CertyoApiDestination",
{
apiDestinationName: "certyo-records-api",
connection,
endpoint: "https://www.certyos.com/api/v1/records",
httpMethod: events.HttpMethod.POST,
rateLimitPerSecond: 100,
description: "Certyo record ingestion endpoint",
}
);
// Custom event bus for your domain events
const bus = new events.EventBus(this, "DomainEventBus", {
eventBusName: "domain-events",
});
// Rule: route matching events to Certyo API Destination
new events.Rule(this, "IngestToCertyoRule", {
eventBus: bus,
ruleName: "route-to-certyo",
description: "Forward domain record events to Certyo for blockchain anchoring",
eventPattern: {
source: ["com.yourorg.orders", "com.yourorg.inventory"],
detailType: ["RecordCreated", "RecordUpdated"],
},
targets: [
new targets.ApiDestination(apiDestination, {
// Transform the EventBridge event into Certyo record format
event: events.RuleTargetInput.fromObject({
tenantId: events.EventField.fromPath("$.detail.tenantId"),
database: events.EventField.fromPath("$.source"),
collection: events.EventField.fromPath("$.detail-type"),
recordId: events.EventField.fromPath("$.detail.recordId"),
recordVersion: events.EventField.fromPath("$.detail.version"),
operationType: "upsert",
recordPayload: events.EventField.fromPath("$.detail.payload"),
sourceTimestamp: events.EventField.fromPath("$.time"),
idempotencyKey: events.EventField.fromPath("$.detail.idempotencyKey"),
}),
deadLetterQueue: dlq,
retryAttempts: 185,
maxEventAge: cdk.Duration.hours(24),
}),
],
});
// Outputs
new cdk.CfnOutput(this, "EventBusArn", { value: bus.eventBusArn });
new cdk.CfnOutput(this, "DlqUrl", { value: dlq.queueUrl });
new cdk.CfnOutput(this, "SecretArn", { value: certyoSecret.secretArn });
}
}{
"Comment": "Certyo record ingestion and verification workflow",
"StartAt": "IngestRecord",
"States": {
"IngestRecord": {
"Type": "Task",
"Resource": "arn:aws:states:::http:invoke",
"Parameters": {
"ApiEndpoint": "https://www.certyos.com/api/v1/records",
"Method": "POST",
"Authentication": {
"ConnectionArn.$": "$.connectionArn"
},
"RequestBody": {
"tenantId.$": "$.tenantId",
"database.$": "$.database",
"collection.$": "$.collection",
"recordId.$": "$.recordId",
"recordVersion.$": "$.recordVersion",
"operationType": "upsert",
"recordPayload.$": "$.recordPayload",
"sourceTimestamp.$": "$$.State.EnteredTime",
"idempotencyKey.$": "States.Format('{}-{}-{}', $.recordId, $.recordVersion, States.ArrayGetItem(States.StringSplit($$.State.EnteredTime, 'T'), 0))"
},
"Headers": {
"Content-Type": "application/json"
}
},
"ResultPath": "$.ingestionResult",
"Retry": [
{
"ErrorEquals": ["States.Http.StatusCode.429", "States.Http.StatusCode.503"],
"IntervalSeconds": 5,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "IngestionFailed",
"ResultPath": "$.error"
}
],
"Next": "WaitForAnchoring"
},
"WaitForAnchoring": {
"Type": "Wait",
"Seconds": 90,
"Comment": "Wait for Certyo's accumulation window (60s) + Polygon confirmation (~30s)",
"Next": "VerifyRecord"
},
"VerifyRecord": {
"Type": "Task",
"Resource": "arn:aws:states:::http:invoke",
"Parameters": {
"ApiEndpoint": "https://www.certyos.com/api/v1/verify/record",
"Method": "POST",
"Authentication": {
"ConnectionArn.$": "$.connectionArn"
},
"RequestBody": {
"tenantId.$": "$.tenantId",
"database.$": "$.database",
"collection.$": "$.collection",
"recordId.$": "$.recordId",
"payload.$": "$.recordPayload"
},
"Headers": {
"Content-Type": "application/json"
}
},
"ResultPath": "$.verificationResult",
"Retry": [
{
"ErrorEquals": ["States.Http.StatusCode.429"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "VerificationFailed",
"ResultPath": "$.error"
}
],
"Next": "CheckVerificationResult"
},
"CheckVerificationResult": {
"Type": "Choice",
"Choices": [
{
"And": [
{ "Variable": "$.verificationResult.ResponseBody.verified", "BooleanEquals": true },
{ "Variable": "$.verificationResult.ResponseBody.anchoredOnChain", "BooleanEquals": true }
],
"Next": "RecordAnchored"
},
{
"Variable": "$.verificationResult.ResponseBody.verified",
"BooleanEquals": false,
"Next": "RetryVerification"
}
],
"Default": "RetryVerification"
},
"RetryVerification": {
"Type": "Pass",
"Parameters": {
"retryCount.$": "States.MathAdd($.retryCount, 1)"
},
"ResultPath": "$.retryState",
"Next": "CheckRetryLimit"
},
"CheckRetryLimit": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.retryState.retryCount",
"NumericGreaterThan": 5,
"Next": "VerificationFailed"
}
],
"Default": "WaitBeforeRetry"
},
"WaitBeforeRetry": {
"Type": "Wait",
"Seconds": 30,
"Next": "VerifyRecord"
},
"RecordAnchored": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn.$": "$.snsTopicArn",
"Subject": "Certyo Record Anchored",
"Message": {
"recordId.$": "$.recordId",
"tenantId.$": "$.tenantId",
"recordHash.$": "$.ingestionResult.ResponseBody.recordHash",
"verified": true,
"anchoredOnChain": true,
"polygonScanUrl.$": "$.verificationResult.ResponseBody.onChainProof.polygonScanEventUrl"
}
},
"End": true
},
"IngestionFailed": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn.$": "$.snsTopicArn",
"Subject": "Certyo Ingestion Failed",
"Message": {
"recordId.$": "$.recordId",
"error.$": "$.error"
}
},
"End": true
},
"VerificationFailed": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn.$": "$.snsTopicArn",
"Subject": "Certyo Verification Failed",
"Message": {
"recordId.$": "$.recordId",
"error": "Verification did not succeed after maximum retries"
}
},
"End": true
}
}
}# Store the Certyo API key in Secrets Manager
aws secretsmanager create-secret \
--name certyo/api-key \
--description "Certyo API key for record ingestion" \
--secret-string '{"apiKey":"certyo_sk_live_your_key_here"}'
# Create an EventBridge Connection with API Key auth
# The connection securely stores the Certyo API key
aws events create-connection \
--name certyo-api-connection \
--authorization-type API_KEY \
--auth-parameters '{
"ApiKeyAuthParameters": {
"ApiKeyName": "X-API-Key",
"ApiKeyValue": "certyo_sk_live_your_key_here"
}
}' \
--description "Connection to Certyo Records API"
# Wait for connection to be AUTHORIZED
aws events describe-connection \
--name certyo-api-connection \
--query 'ConnectionState'
# Create the API Destination pointing to Certyo
CONNECTION_ARN=$(aws events describe-connection \
--name certyo-api-connection \
--query 'ConnectionArn' --output text)
aws events create-api-destination \
--name certyo-records-api \
--connection-arn $CONNECTION_ARN \
--invocation-endpoint "https://www.certyos.com/api/v1/records" \
--http-method POST \
--invocation-rate-limit-per-second 100 \
--description "Certyo record ingestion endpoint"
# Create a custom event bus
aws events create-event-bus --name domain-events
# Create a dead-letter queue for failed deliveries
aws sqs create-queue \
--queue-name certyo-eventbridge-dlq \
--attributes '{
"MessageRetentionPeriod": "1209600",
"SqsManagedSseEnabled": "true"
}'
DLQ_ARN=$(aws sqs get-queue-attributes \
--queue-url $(aws sqs get-queue-url --queue-name certyo-eventbridge-dlq --query 'QueueUrl' --output text) \
--attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
# Create a rule that matches domain record events
API_DEST_ARN=$(aws events describe-api-destination \
--name certyo-records-api \
--query 'ApiDestinationArn' --output text)
# Create IAM role for EventBridge to invoke the API Destination
ROLE_ARN=$(aws iam create-role \
--role-name EventBridgeCertyoRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "events.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}' --query 'Role.Arn' --output text)
aws iam put-role-policy \
--role-name EventBridgeCertyoRole \
--policy-name InvokeApiDestination \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["events:InvokeApiDestination"],
"Resource": ["'"$API_DEST_ARN"'"]
}]
}'
aws events put-rule \
--name route-to-certyo \
--event-bus-name domain-events \
--event-pattern '{
"source": ["com.yourorg.orders", "com.yourorg.inventory"],
"detail-type": ["RecordCreated", "RecordUpdated"]
}' \
--description "Forward domain record events to Certyo"
# Attach the API Destination as the rule target with input transformation
aws events put-targets \
--rule route-to-certyo \
--event-bus-name domain-events \
--targets '[{
"Id": "certyo-api-destination",
"Arn": "'"$API_DEST_ARN"'",
"RoleArn": "'"$ROLE_ARN"'",
"InputTransformer": {
"InputPathsMap": {
"tenantId": "$.detail.tenantId",
"database": "$.source",
"collection": "$.detail-type",
"recordId": "$.detail.recordId",
"version": "$.detail.version",
"payload": "$.detail.payload",
"timestamp": "$.time",
"idempotencyKey": "$.detail.idempotencyKey"
},
"InputTemplate": "{"tenantId": <tenantId>, "database": <database>, "collection": <collection>, "recordId": <recordId>, "recordVersion": <version>, "operationType": "upsert", "recordPayload": <payload>, "sourceTimestamp": <timestamp>, "idempotencyKey": <idempotencyKey>}"
},
"RetryPolicy": {
"MaximumRetryAttempts": 185,
"MaximumEventAgeInSeconds": 86400
},
"DeadLetterConfig": {
"Arn": "'"$DLQ_ARN"'"
}
}]'
echo "EventBridge pipeline configured. Send events to the 'domain-events' bus."
echo "Events matching the rule will be forwarded to Certyo automatically."Authentification
Le modele d'authentification recommande utilise la gestion native des secrets AWS avec IAM pour l'autorisation de service a service :
- Secrets Manager — Stockez le
X-API-KeyCertyo comme un secret. Activez la rotation automatique si votre organisation l'exige. - EventBridge Connection — L'objet de connexion stocke en securite la cle API et l'injecte dans chaque requete d'API Destination. Vous ne gerez pas la cle dans le code applicatif.
- Role d'execution Lambda — Accordez aux fonctions Lambda
secretsmanager:GetSecretValuesur l'ARN specifique du secret. Utilisez le principe du moindre privilege. - Cles API Gateway — Pour les consommateurs internes, emettez des cles API Gateway liees aux plans d'utilisation. La passerelle injecte la cle Certyo via un autoriseur Lambda ou un template de mapping.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadCertyoApiKey",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:certyo/api-key-*"
}
]
}Optimisation des couts
Choisissez le bon modele d'integration en fonction de votre charge de travail pour minimiser les couts AWS :
- EventBridge API Destinations (le moins cher) — Pas de couts d'invocation Lambda. Vous ne payez que pour les evenements EventBridge (1,00 $ par million d'evenements) et les invocations d'API Destination. Utilisez ceci pour le transfert simple d'evenements sans transformation.
- EventBridge + Input Transformer — Si vous avez seulement besoin de remodeler le payload de l'evenement (renommer des champs, extraire des valeurs imbriquees), utilisez l'Input Transformer d'EventBridge au lieu de Lambda. Zero cout de calcul.
- Lambda (pour les transformations) — Reservez Lambda pour les cas ou vous devez lire depuis S3, appeler des API externes ou appliquer une logique metier complexe avant l'ingestion. Utilisez ARM64 (Graviton) pour 20 % d'economie.
- Step Functions Express — Pour les workflows de verification a haut volume (>100K executions/mois), les Express Workflows coutent jusqu'a 80 % de moins que les Standard Workflows.
Prochaines etapes
- Deployez le stack CDK ci-dessus pour provisionner le pipeline EventBridge complet en quelques minutes
- Publiez un evenement de test sur le bus
domain-eventset verifiez qu'il arrive dans Certyo - Deployez le workflow de verification Step Functions pour une confirmation d'ancrage de bout en bout
- Consultez le Guide d'ingestion pour les details sur le schema d'enregistrement et l'idempotence
- Voir le Guide de verification pour les details sur les preuves cryptographiques
AI Integration Skill
Download a skill file that enables AI agents to generate working AWS + Certyo integration code for any language or framework.
What's inside
- Authentification — EventBridge Connection avec API Key, Lambda avec Secrets Manager
- Architecture — EventBridge → API Destination ou Lambda → Certyo → Step Functions verification
- Exemples de code — Python Lambda, stack CDK TypeScript, workflow Step Functions ASL
- API Destinations — Transfert direct vers Certyo sans Lambda avec politiques de reessai
- Step Functions — Workflow de verification avec etats Wait, boucles de reessai et branchement
- Optimisation des couts — Comparaison API Destinations vs Lambda vs Express Workflows
How to use
Claude Code
Place the file in your project's .claude/commands/ directory, then use it as a slash command:
# Download the skill file
mkdir -p .claude/commands
curl -o .claude/commands/certyo-aws.md \
https://www.certyos.com/developers/skills/certyo-aws-skill.md
# Use it in Claude Code
/certyo-aws "Generez une API Destination EventBridge qui transmet les evenements a Certyo"Cursor / Copilot / Any AI Agent
Add the file to your project root or attach it to a conversation. The AI agent will use the AWS-specific patterns, field mappings, and code examples to generate correct integration code.
# Add to your project
curl -o CERTYO_AWS.md \
https://www.certyos.com/developers/skills/certyo-aws-skill.md
# Then in your AI agent:
"Using the Certyo AWS spec in CERTYO_AWS.md,
generez une api destination eventbridge qui transmet les evenements a certyo"CLAUDE.md Context File
Append the skill file to your project's CLAUDE.md so every Claude conversation has AWS + Certyo context automatically.
# Append to your project's CLAUDE.md
echo "" >> CLAUDE.md
echo "## Certyo AWS Integration" >> CLAUDE.md
cat CERTYO_AWS.md >> CLAUDE.md