Microsoft Dynamics 365
Connectez Dynamics 365 Business Central et Finance & Operations a Certyo pour des certificats d'authenticite ancres sur la blockchain pour chaque expedition, mouvement de stock et transaction de vente.
Apercu
Dynamics 365 couvre deux familles de produits distinctes avec des surfaces d'integration differentes. Ce guide fournit des modeles prets pour la production pour les deux :
- Business Central — Extensions AL, API Pages OData v4 et webhooks pour les clients PME utilisant Business Central Online ou on-premise.
- Finance & Operations (F&O) — Dataverse Dual-Write, Business Events et endpoints OData Data Entity pour les clients entreprise utilisant Supply Chain Management, Commerce ou Finance.
Prerequis
- Tenant Azure avec Microsoft Entra ID (anciennement Azure AD)
- Abonnement Dynamics 365 Business Central ou Finance & Operations
- Cle API Certyo depuis le backoffice (
certyo_sk_live_...) - Namespace Azure Service Bus (niveau Standard ou superieur)
- Runtime Azure Functions (v4, .NET 8 isolated worker)
Architecture
Les deux modeles d'integration suivent le meme flux evenementiel. L'ERP emet un evenement de domaine, l'infrastructure Azure decouple les systemes et une fonction serverless appelle Certyo :
┌─────────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Dynamics 365 │ │ Azure │ │ Azure Function │ │ Certyo API │
│ │────>│ Service Bus │────>│ │────>│ │
│ Business Central │ │ │ │ CertyoIngest │ │ POST │
│ or F&O │ │ certyo-records │ │ Function │ │ /api/v1/ │
│ │ │ queue │ │ │ │ records │
└─────────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────┘
│ │
│ Business Central: │ Polls Certyo for
│ AL Codeunit → HttpClient │ anchor status, then
│ or Webhook subscription │ writes certificate
│ │ back to D365 via
│ Finance & Operations: │ OData PATCH
│ Business Event → Service Bus │
│ or Dataverse Dual-Write trigger │
└─────────────────────────────────────────────────────┘Integration Business Central
Business Central expose les donnees via des API Pages OData v4 et supporte les extensions AL pour la logique personnalisee. Le modele recommande consiste a emettre un message Service Bus depuis un Codeunit AL lors de la validation d'une expedition de vente.
Option A : Extension AL (recommandee)
Abonnez-vous a l'evenement OnAfterPostSalesShipment, construisez le payload d'enregistrement Certyo et envoyez-le a Azure Service Bus. La Azure Function le recupere et appelle Certyo.
Option B : API Page OData v4 + Power Automate
Exposez une API Page personnalisee avec les champs necessaires a Certyo, puis utilisez Power Automate pour interroger les nouvelles expeditions et les transmettre. Cette approche est moins technique mais introduit une latence de polling.
Integration Finance & Operations
F&O fournit les Business Events et Dataverse Dual-Write pour l'integration en temps reel. Le modele recommande utilise les Business Events pour envoyer les evenements de transaction de stock a Azure Service Bus, ou une Azure Function les transforme et les transmet a Certyo.
Modele Dataverse Dual-Write
Lorsqu'une reception de marchandises ou un transfert de stock est valide dans F&O, Dual-Write synchronise l'entite vers Dataverse. Un flux Power Automate ou un plugin Dataverse detecte le changement de ligne et appelle Certyo. Ce modele fonctionne bien si vous utilisez deja Dual-Write pour d'autres integrations.
Modele Business Events (recommande)
Enregistrez un Business Event sur l'entite InventTransPosting. Configurez l'endpoint pour livrer a Azure Service Bus. La Azure Function consomme le message et appelle Certyo avec les details du document materiel.
Exemples de code
using Azure.Messaging.ServiceBus;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using System.Net.Http.Json;
using System.Text.Json;
namespace Certyo.Dynamics365.Functions;
public sealed class CertyoIngestFunction
{
private readonly HttpClient _http;
private readonly ILogger<CertyoIngestFunction> _logger;
public CertyoIngestFunction(
IHttpClientFactory httpClientFactory,
ILogger<CertyoIngestFunction> logger)
{
_http = httpClientFactory.CreateClient("Certyo");
_logger = logger;
}
[Function("CertyoIngest")]
public async Task Run(
[ServiceBusTrigger("certyo-records", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message,
CancellationToken cancellationToken)
{
var d365Event = JsonSerializer.Deserialize<D365ShipmentEvent>(
message.Body.ToString(),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (d365Event is null)
{
_logger.LogWarning("Received null event, skipping");
return;
}
var certyoRecord = new
{
tenantId = Environment.GetEnvironmentVariable("CERTYO_TENANT_ID"),
database = "dynamics365",
collection = d365Event.EntityName,
recordId = d365Event.DocumentNo,
recordVersion = d365Event.Version ?? "1",
operationType = "insert",
sourceTimestamp = d365Event.PostingDate.ToString("O"),
idempotencyKey = $"{d365Event.DocumentNo}-{d365Event.Version ?? "1"}-{d365Event.PostingDate:yyyy-MM-dd}",
recordPayload = new
{
documentNo = d365Event.DocumentNo,
entityName = d365Event.EntityName,
sellToCustomerNo = d365Event.SellToCustomerNo,
sellToCustomerName = d365Event.SellToCustomerName,
shipToCode = d365Event.ShipToCode,
postingDate = d365Event.PostingDate,
shipmentMethodCode = d365Event.ShipmentMethodCode,
trackingNo = d365Event.TrackingNo,
lines = d365Event.Lines.Select(l => new
{
lineNo = l.LineNo,
itemNo = l.ItemNo,
description = l.Description,
quantity = l.Quantity,
unitOfMeasureCode = l.UnitOfMeasureCode,
lotNo = l.LotNo,
serialNo = l.SerialNo
})
}
};
var response = await _http.PostAsJsonAsync(
"/api/v1/records",
certyoRecord,
cancellationToken);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync(cancellationToken);
_logger.LogError(
"Certyo ingestion failed for {DocumentNo}: {Status} {Body}",
d365Event.DocumentNo, response.StatusCode, body);
throw new InvalidOperationException(
$"Certyo returned {response.StatusCode}");
}
var result = await response.Content
.ReadFromJsonAsync<JsonElement>(cancellationToken);
_logger.LogInformation(
"Ingested {DocumentNo} -> recordHash: {Hash}",
d365Event.DocumentNo,
result.GetProperty("recordHash").GetString());
}
}
public record D365ShipmentEvent
{
public string DocumentNo { get; init; } = "";
public string EntityName { get; init; } = "SalesShipmentHeader";
public string SellToCustomerNo { get; init; } = "";
public string SellToCustomerName { get; init; } = "";
public string ShipToCode { get; init; } = "";
public DateTime PostingDate { get; init; }
public string ShipmentMethodCode { get; init; } = "";
public string TrackingNo { get; init; } = "";
public string? Version { get; init; }
public List<ShipmentLine> Lines { get; init; } = new();
}
public record ShipmentLine
{
public int LineNo { get; init; }
public string ItemNo { get; init; } = "";
public string Description { get; init; } = "";
public decimal Quantity { get; init; }
public string UnitOfMeasureCode { get; init; } = "";
public string? LotNo { get; init; }
public string? SerialNo { get; init; }
}Cette definition de flux Power Automate se declenche lorsqu'une ligne Dataverse change sur l'entite msdyn_inventorytransaction (scenario F&O Dual-Write) et appelle Certyo. Importez ce JSON dans Power Automate > Mes flux > Importer.
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"triggers": {
"When_a_row_is_added_or_modified": {
"type": "OpenApiConnectionWebhook",
"inputs": {
"host": {
"connectionName": "shared_commondataserviceforapps",
"operationId": "SubscribeWebhookTrigger",
"apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
},
"parameters": {
"subscriptionRequest/message": 4,
"subscriptionRequest/entityname": "msdyn_inventorytransaction",
"subscriptionRequest/scope": 4,
"subscriptionRequest/filterexpression": "msdyn_transactiontype eq 100000000"
}
}
}
},
"actions": {
"Initialize_Certyo_Payload": {
"type": "Compose",
"inputs": {
"tenantId": "@{parameters('CertyoTenantId')}",
"database": "dynamics365-fo",
"collection": "InventoryTransactions",
"recordId": "@{triggerOutputs()?['body/msdyn_inventorytransactionid']}",
"operationType": "insert",
"sourceTimestamp": "@{triggerOutputs()?['body/modifiedon']}",
"idempotencyKey": "@{concat(triggerOutputs()?['body/msdyn_inventorytransactionid'], '-', formatDateTime(triggerOutputs()?['body/modifiedon'], 'yyyy-MM-dd'))}",
"recordPayload": {
"transactionId": "@{triggerOutputs()?['body/msdyn_inventorytransactionid']}",
"itemNumber": "@{triggerOutputs()?['body/msdyn_itemnumber']}",
"productName": "@{triggerOutputs()?['body/msdyn_productname']}",
"transactionType": "@{triggerOutputs()?['body/msdyn_transactiontype']}",
"quantity": "@{triggerOutputs()?['body/msdyn_quantity']}",
"unit": "@{triggerOutputs()?['body/msdyn_unitid']}",
"warehouse": "@{triggerOutputs()?['body/msdyn_warehouseid']}",
"batchNumber": "@{triggerOutputs()?['body/msdyn_batchnumber']}",
"serialNumber": "@{triggerOutputs()?['body/msdyn_serialnumber']}",
"inventorySiteId": "@{triggerOutputs()?['body/msdyn_inventorysiteid']}",
"transactionDate": "@{triggerOutputs()?['body/msdyn_transactiondate']}"
}
},
"runAfter": {}
},
"Call_Certyo_Ingest": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://www.certyos.com/api/v1/records",
"headers": {
"X-API-Key": "@{parameters('CertyoApiKey')}",
"Content-Type": "application/json"
},
"body": "@outputs('Initialize_Certyo_Payload')"
},
"runAfter": {
"Initialize_Certyo_Payload": ["Succeeded"]
},
"runtimeConfiguration": {
"contentTransfer": { "transferMode": "Chunked" }
}
},
"Check_Response": {
"type": "If",
"expression": {
"and": [
{ "equals": ["@outputs('Call_Certyo_Ingest')['statusCode']", 202] }
]
},
"actions": {
"Log_Success": {
"type": "Compose",
"inputs": "Certyo accepted record @{triggerOutputs()?['body/msdyn_inventorytransactionid']} with hash @{body('Call_Certyo_Ingest')?['recordHash']}"
}
},
"else": {
"actions": {
"Log_Failure": {
"type": "Compose",
"inputs": "Certyo rejected record: @{body('Call_Certyo_Ingest')}"
}
}
},
"runAfter": {
"Call_Certyo_Ingest": ["Succeeded", "Failed"]
}
}
},
"parameters": {
"CertyoTenantId": {
"type": "String",
"defaultValue": ""
},
"CertyoApiKey": {
"type": "SecureString",
"defaultValue": ""
}
}
}
}Ce codeunit AL s'abonne a l'evenement de validation d'expedition de vente dans Business Central et envoie les donnees d'expedition a Azure Service Bus. La Azure Function (montree dans l'onglet C#) les recupere et les transmet a Certyo.
// AL Extension for Business Central
// Language: AL (displayed as TypeScript — no AL syntax option)
codeunit 50100 "Certyo Shipment Publisher"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterPostSalesDoc', '', false, false)]
local procedure OnAfterPostSalesDoc(
var SalesHeader: Record "Sales Header";
var GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line";
SalesShptHdrNo: Code[20];
RetRcpHdrNo: Code[20];
SalesInvHdrNo: Code[20];
SalesCrMemoHdrNo: Code[20])
var
SalesShipmentHeader: Record "Sales Shipment Header";
SalesShipmentLine: Record "Sales Shipment Line";
ServiceBusHelper: Codeunit "Certyo Service Bus Helper";
JsonPayload: JsonObject;
LinesArray: JsonArray;
LineObject: JsonObject;
begin
if SalesShptHdrNo = '' then
exit;
SalesShipmentHeader.Get(SalesShptHdrNo);
JsonPayload.Add('DocumentNo', SalesShipmentHeader."No.");
JsonPayload.Add('EntityName', 'SalesShipmentHeader');
JsonPayload.Add('SellToCustomerNo', SalesShipmentHeader."Sell-to Customer No.");
JsonPayload.Add('SellToCustomerName', SalesShipmentHeader."Sell-to Customer Name");
JsonPayload.Add('ShipToCode', SalesShipmentHeader."Ship-to Code");
JsonPayload.Add('PostingDate', Format(SalesShipmentHeader."Posting Date", 0, '<Year4>-<Month,2>-<Day,2>'));
JsonPayload.Add('ShipmentMethodCode', SalesShipmentHeader."Shipment Method Code");
JsonPayload.Add('TrackingNo', SalesShipmentHeader."Package Tracking No.");
SalesShipmentLine.SetRange("Document No.", SalesShptHdrNo);
if SalesShipmentLine.FindSet() then
repeat
Clear(LineObject);
LineObject.Add('LineNo', SalesShipmentLine."Line No.");
LineObject.Add('ItemNo', SalesShipmentLine."No.");
LineObject.Add('Description', SalesShipmentLine.Description);
LineObject.Add('Quantity', SalesShipmentLine.Quantity);
LineObject.Add('UnitOfMeasureCode', SalesShipmentLine."Unit of Measure Code");
LinesArray.Add(LineObject);
until SalesShipmentLine.Next() = 0;
JsonPayload.Add('Lines', LinesArray);
ServiceBusHelper.SendToQueue('certyo-records', JsonPayload);
end;
}
codeunit 50101 "Certyo Service Bus Helper"
{
procedure SendToQueue(QueueName: Text; Payload: JsonObject)
var
HttpClient: HttpClient;
HttpContent: HttpContent;
HttpHeaders: HttpHeaders;
HttpResponseMessage: HttpResponseMessage;
ServiceBusUri: Text;
SasToken: Text;
PayloadText: Text;
begin
ServiceBusUri := GetServiceBusUri(QueueName);
SasToken := GenerateSasToken(QueueName);
Payload.WriteTo(PayloadText);
HttpContent.WriteFrom(PayloadText);
HttpContent.GetHeaders(HttpHeaders);
HttpHeaders.Remove('Content-Type');
HttpHeaders.Add('Content-Type', 'application/json');
HttpHeaders.Add('Authorization', SasToken);
HttpClient.Post(ServiceBusUri, HttpContent, HttpResponseMessage);
if not HttpResponseMessage.IsSuccessStatusCode() then
Error('Failed to send to Service Bus: %1', HttpResponseMessage.ReasonPhrase());
end;
local procedure GetServiceBusUri(QueueName: Text): Text
var
Setup: Record "Certyo Setup";
begin
Setup.Get();
exit(StrSubstNo('https://%1.servicebus.windows.net/%2/messages',
Setup."Service Bus Namespace", QueueName));
end;
local procedure GenerateSasToken(QueueName: Text): Text
var
Setup: Record "Certyo Setup";
CryptographyMgmt: Codeunit "Cryptography Management";
Uri: Text;
Expiry: Integer;
StringToSign: Text;
Signature: Text;
begin
Setup.Get();
Uri := GetServiceBusUri(QueueName);
Expiry := GetUnixTimestamp() + 3600;
StringToSign := Uri + '\n' + Format(Expiry);
Signature := CryptographyMgmt.GenerateHashAsBase64String(
StringToSign, Setup."Service Bus SAS Key",
2); // HMACSHA256
exit(StrSubstNo('SharedAccessSignature sr=%1&sig=%2&se=%3&skn=%4',
Uri, Signature, Expiry, Setup."Service Bus SAS Key Name"));
end;
local procedure GetUnixTimestamp(): Integer
begin
exit(Round((CurrentDateTime() - CreateDateTime(19700101D, 0T)) / 1000, 1));
end;
}Configuration de la Azure Function
Enregistrez le HttpClient nomme Certyo dans Program.cs pour que la fonction puisse appeler l'API avec l'adresse de base et l'en-tete de cle API corrects :
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddHttpClient("Certyo", client =>
{
client.BaseAddress = new Uri("https://www.certyos.com");
client.DefaultRequestHeaders.Add(
"X-API-Key",
Environment.GetEnvironmentVariable("CERTYO_API_KEY"));
});
})
.Build();
host.Run();Parametres d'application requis
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"ServiceBusConnection": "<your-service-bus-connection-string>",
"CERTYO_API_KEY": "certyo_sk_live_abc123...",
"CERTYO_TENANT_ID": "your-tenant-id"
}
}Authentification
Cote Dynamics 365
Enregistrez une App Registration dans Microsoft Entra ID avec la permission API Dynamics 365 Business Central ou Dynamics 365 Finance and Operations. Utilisez le flux OAuth 2.0 client credentials pour obtenir un jeton Bearer :
curl -X POST "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id={app-id}" \
-d "client_secret={client-secret}" \
-d "scope=https://api.businesscentral.dynamics.com/.default" \
-d "grant_type=client_credentials"Pour Finance & Operations, remplacez le scope par https://your-environment.operations.dynamics.com/.default.
Cote Certyo
Certyo utilise une simple cle API dans l'en-tete X-API-Key. Stockez la cle dans Azure Key Vault et referencez-la depuis les parametres de votre Function App via des references Key Vault :
# Key Vault reference syntax in Azure Function app settings
CERTYO_API_KEY=@Microsoft.KeyVault(SecretUri=https://your-vault.vault.azure.net/secrets/certyo-api-key/)Limites de debit et strategie de regroupement
Dynamics 365 impose des limites de debit API que vous devez prendre en compte dans votre integration :
- Business Central: 6 000 requetes API OData par fenetre glissante de 5 minutes par environnement. Le depassement retourne 429 Too Many Requests.
- Finance & Operations: 6 000 requetes par fenetre de 5 minutes par utilisateur, avec un systeme de limitation base sur la priorite en charge maximale.
- Certyo: L'endpoint d'enregistrement unitaire a des limites genereuses, mais pour les scenarios a haut volume, utilisez POST /api/v1/records/bulk (jusqu'a 1 000 enregistrements par requete).
Strategie de regroupement recommandee : Si votre environnement D365 traite plus de 100 expeditions par minute, accumulez les evenements dans la file Service Bus et configurez la Azure Function avec un declencheur batch avec MaxMessageBatchSize = 100. Mappez chaque lot vers un seul appel POST /api/v1/records/bulk :
[Function("CertyoBatchIngest")]
public async Task RunBatch(
[ServiceBusTrigger("certyo-records", Connection = "ServiceBusConnection",
IsBatched = true)]
ServiceBusReceivedMessage[] messages,
CancellationToken cancellationToken)
{
var records = messages
.Select(m => JsonSerializer.Deserialize<D365ShipmentEvent>(m.Body.ToString()))
.Where(e => e is not null)
.Select(e => new
{
database = "dynamics365",
collection = e!.EntityName,
recordId = e.DocumentNo,
recordVersion = e.Version ?? "1",
operationType = "insert",
sourceTimestamp = e.PostingDate.ToString("O"),
idempotencyKey = $"{e.DocumentNo}-{e.Version ?? "1"}-{e.PostingDate:yyyy-MM-dd}",
recordPayload = new
{
documentNo = e.DocumentNo,
entityName = e.EntityName,
sellToCustomerNo = e.SellToCustomerNo,
sellToCustomerName = e.SellToCustomerName,
postingDate = e.PostingDate,
trackingNo = e.TrackingNo
}
})
.ToList();
var bulkPayload = new
{
tenantId = Environment.GetEnvironmentVariable("CERTYO_TENANT_ID"),
records
};
var response = await _http.PostAsJsonAsync(
"/api/v1/records/bulk", bulkPayload, cancellationToken);
response.EnsureSuccessStatusCode();
_logger.LogInformation("Bulk ingested {Count} records", records.Count);
}Verification et ecriture retour
Apres l'ancrage des enregistrements (generalement 60-90 secondes), vous pouvez les verifier et reecrire le statut du certificat dans les champs personnalises de Dynamics 365. Utilisez une Azure Function declenchee par timer qui interroge Certyo et met a jour D365 via OData :
[Function("CertyoVerifyWriteBack")]
public async Task RunVerify(
[TimerTrigger("0 */2 * * * *")] TimerInfo timer,
CancellationToken cancellationToken)
{
var tenantId = Environment.GetEnvironmentVariable("CERTYO_TENANT_ID");
// Query Certyo for recently ingested records pending verification
var queryResponse = await _http.GetAsync(
$"/api/v1/records?tenantId={tenantId}&database=dynamics365&status=Anchored&verifiedInErp=false&limit=50",
cancellationToken);
var records = await queryResponse.Content
.ReadFromJsonAsync<RecordQueryResult>(cancellationToken);
foreach (var record in records?.Items ?? [])
{
// Verify the record on-chain
var verifyResponse = await _http.PostAsJsonAsync(
"/api/v1/verify/record",
new
{
tenantId,
database = record.Database,
collection = record.Collection,
recordId = record.RecordId,
payload = record.RecordPayload
},
cancellationToken);
var verification = await verifyResponse.Content
.ReadFromJsonAsync<JsonElement>(cancellationToken);
if (verification.GetProperty("verified").GetBoolean())
{
// Write certificate data back to D365 via OData
var d365Token = await GetD365AccessToken(cancellationToken);
var patchPayload = new
{
certyoVerified = true,
certyoRecordHash = verification.GetProperty("snapshotHash").GetString(),
certyoMerkleRoot = verification.GetProperty("merkleRoot").GetString(),
certyoPolygonTx = verification.GetProperty("onChainProof")
.GetProperty("polygonScanEventUrl").GetString(),
certyoVerifiedAt = DateTime.UtcNow
};
using var patchRequest = new HttpRequestMessage(
HttpMethod.Patch,
$"https://api.businesscentral.dynamics.com/v2.0/{tenantId}/Production/api/v2.0/companies({{companyId}})/salesShipments({{shipmentId}})");
patchRequest.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", d365Token);
patchRequest.Content = JsonContent.Create(patchPayload);
await new HttpClient().SendAsync(patchRequest, cancellationToken);
_logger.LogInformation(
"Wrote certificate back to D365 for {RecordId}: {TxUrl}",
record.RecordId,
patchPayload.certyoPolygonTx);
}
}
}Tests
- Deployez la Azure Function sur un slot de staging et configurez-la avec votre cle sandbox Certyo. (
certyo_sk_test_...) - Validez une expedition de vente dans Business Central (ou une reception de marchandises dans F&O) et confirmez que le message apparait dans la file Service Bus.
- Verifiez que la Azure Function a traite le message en consultant les logs Application Insights pour la sortie recordHash.
- Attendez 60-90 secondes, puis appelez POST /api/v1/verify/record avec les donnees d'expedition pour confirmer l'ancrage on-chain.
- Verifiez l'enregistrement d'expedition de vente D365 pour les champs personnalises Certyo remplis.
AI Integration Skill
Download a skill file that enables AI agents to generate working Microsoft Dynamics 365 + Certyo integration code for any language or framework.
What's inside
- Authentication — OAuth 2.0 client credentials avec Entra ID et configuration Key Vault
- Architecture — Evenement D365 → Azure Service Bus → Azure Function → pipeline Certyo
- Field mapping — Champs SalesShipmentHeader, Item et Line vers le schema d'enregistrement Certyo
- Code examples — Azure Function C#, extension AL pour Business Central, flux Power Automate
- Verification — Polling TimerTrigger et ecriture retour OData PATCH vers les champs personnalises D365
- Rate limiting — OData D365 6 000 req/fenetre de 5 min et strategies de regroupement
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-dynamics365.md \
https://www.certyos.com/developers/skills/certyo-dynamics365-skill.md
# Use it in Claude Code
/certyo-dynamics365 "Generez une Azure Function qui ingere les evenements d'expedition D365 dans 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 Microsoft Dynamics 365-specific patterns, field mappings, and code examples to generate correct integration code.
# Add to your project
curl -o CERTYO_DYNAMICS365.md \
https://www.certyos.com/developers/skills/certyo-dynamics365-skill.md
# Then in your AI agent:
"Using the Certyo Microsoft Dynamics 365 spec in CERTYO_DYNAMICS365.md,
generez une azure function qui ingere les evenements d'expedition d365 dans certyo"CLAUDE.md Context File
Append the skill file to your project's CLAUDE.md so every Claude conversation has Microsoft Dynamics 365 + Certyo context automatically.
# Append to your project's CLAUDE.md
echo "" >> CLAUDE.md
echo "## Certyo Microsoft Dynamics 365 Integration" >> CLAUDE.md
cat CERTYO_DYNAMICS365.md >> CLAUDE.md