Depois de muita pesquisa e testes eis a solução que eu encontrei utilizando o N8N:
1- Envio do arquivo de imagem via Weebhook:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Enviar Arquivo</title>
</head>
<body>
<h2>Enviar arquivo via Webhook</h2>
<input type="file" id="arquivo" />
<br><br>
<button onclick="enviarArquivo()">Enviar</button>
<pre id="resultado"></pre>
<script src="enviar.js"></script>
</body>
</html>
function enviarArquivo() {
const input = document.getElementById("arquivo");
const resultado = document.getElementById("resultado");
if (!input.files.length) {
alert("Selecione um arquivo");
return;
}
const file = input.files[0];
const reader = new FileReader();
reader.onload = function () {
// remove "data:image/png;base64," ou similar
const base64 = reader.result.split(",")[1];
const payload = {
nomeArquivo: file.name,
tipo: file.type,
tamanho: file.size,
conteudoBase64: base64
};
fetch("https://WEEBHOOKLINK", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(res => res.text())
.then(res => {
resultado.textContent = "Enviado com sucesso!\n\n" + res;
})
.catch(err => {
resultado.textContent = "Erro ao enviar:\n" + err;
});
};
reader.readAsDataURL(file);
}
2- Recebe Imagem via Weebhook > Autentica o Gateway > Destaca o Bearer Token > Inclui dados e imagem via API:
{
"name": "INCLUIR DADOS",
"nodes": [
{
"parameters": {
"method": "POST",
"url": "https://api.sandbox.sankhya.com.br/authenticate",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/x-www-form-urlencoded"
},
{
"name": "Accept",
"value": "application/x-www-form-urlencoded"
},
{
"name": "X-Token",
"value": "dc6af5b5-e78e-4e40-b8f6-710cb651eefd"
}
]
},
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "grant_type",
"value": "client_credentials"
},
{
"name": "client_id",
"value": "KEY GERADA NO SANKHYA"
},
{
"name": "client_secret",
"value": "KEY GERADA NO SANKHYA"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
-304,
-240
],
"id": "1b5027bd-eab1-4f1a-ab36-ee4b614c3e86",
"name": "Validação"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "b41550fd-d816-4171-aa47-2205c55fd3bf",
"name": "token",
"value": "={{ $json.access_token }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-112,
-240
],
"id": "df92bc05-d5cd-4065-bc8b-0ac6fa6bd7b4",
"name": "Token"
},
{
"parameters": {
"httpMethod": "POST",
"path": "23055dd3-1e49-4d2f-ab04-7f28a4b356b7",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-560,
-240
],
"id": "6f2bd181-8b97-4b0d-9b71-ac3e0e197cc8",
"name": "Webhook",
"webhookId": "23055dd3-1e49-4d2f-ab04-7f28a4b356b7"
},
{
"parameters": {
"method": "POST",
"url": "https://api.sandbox.sankhya.com.br/gateway/v1/mge/service.sbr?serviceName=DatasetSP.save&outputType=json",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "serviceName",
"value": "DatasetSP.save"
},
{
"name": "outputType",
"value": "json"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"serviceName\": \"DatasetSP.save\",\n \"requestBody\": {\n \"entityName\": \"AD_TESTE\",\n \"standAlone\": false,\n \"fields\": [\"ID\", \"NOME\", \"NUMERO\", \"BASE64\"],\n \"records\": [\n {\n \"values\": {\n \"1\": \"sasuke\",\n \"2\": \"23\",\n \"3\": \"{{ $('Webhook').item.json.body.conteudoBase64 }}\"\n }\n }\n ]\n }\n}\n",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
128,
-240
],
"id": "3ab2bf75-0fa3-49ab-89aa-416e5684626b",
"name": "Roda consulta"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
368,
-240
],
"id": "43ddae43-30fc-43b0-8b05-9587d10f46ce",
"name": "Respond to Webhook"
}
],
"pinData": {},
"connections": {
"Validação": {
"main": [
[
{
"node": "Token",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Validação",
"type": "main",
"index": 0
}
]
]
},
"Token": {
"main": [
[
{
"node": "Roda consulta",
"type": "main",
"index": 0
}
]
]
},
"Roda consulta": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Respond to Webhook": {
"main": [
[]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "6d52b285-bfbe-4db0-a62f-de9a883866ca",
"meta": {
"instanceId": "949b8d98ae27b5da298f87134ef7188484791002d472dbe6a2bb2d1ace6650a9"
},
"id": "hnQrpCGOeY04cuxk",
"tags": []
}3- Se tentar enviar uma arquivo Base64 direto para um campo BLOB vai ser salvo porém não visualizável e não vai aparecer no output apenas no DB, a melhor forma é mandar para um campo CLOB e depois converter para Binário via trigger.
Segue exemplo funcional em uma tabela teste:
CREATE OR REPLACE TRIGGER TRG_AD_TESTE_IMG
BEFORE INSERT OR UPDATE ON AD_TESTE
FOR EACH ROW
DECLARE
v_blob BLOB;
v_clob CLOB := :NEW.BASE64;
v_len NUMBER;
v_offset NUMBER := 1;
v_buffer_raw RAW(32767);
v_buffer_vc VARCHAR2(32767);
v_chunk_size NUMBER := 12000;
BEGIN
-- Só executa se a flag for 'S'
IF :NEW.CONVERTE = 'S' AND v_clob IS NOT NULL THEN
DBMS_LOB.CREATETEMPORARY(v_blob, TRUE);
v_len := DBMS_LOB.GETLENGTH(v_clob);
-- Loop para converter o CLOB em pedaços e montar o BLOB
WHILE v_offset <= v_len LOOP
v_buffer_vc := DBMS_LOB.SUBSTR(v_clob, v_chunk_size, v_offset);
-- Converte o pedaço de texto para binário
v_buffer_raw := UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW(v_buffer_vc));
-- Escreve no BLOB final
DBMS_LOB.WRITEAPPEND(v_blob, UTL_RAW.LENGTH(v_buffer_raw), v_buffer_raw);
v_offset := v_offset + v_chunk_size;
END LOOP;
-- Atribui o BLOB resultante ao campo de imagem
:NEW.IMAGEM := v_blob;
-- Limpa a flag e o campo temporário para economizar espaço
:NEW.CONVERTE := 'N';
:NEW.BASE64 := NULL;
DBMS_LOB.FREETEMPORARY(v_blob);
END IF;
EXCEPTION
WHEN OTHERS THEN
IF v_blob IS NOT NULL THEN DBMS_LOB.FREETEMPORARY(v_blob); END IF;
RAISE_APPLICATION_ERROR(-20001, 'Erro ao processar imagem Base64: ' || SQLERRM);
END;