Ler o feed de eventos
O feed devolve os eventos da organização em ordem de sequence, com o mesmo corpo do webhook. A leitura guarda o next de cada página e para quando uma página vem vazia. As regras estão em Webhook de venda.
- Guarde o cursor de forma durável (banco, não memória), e só depois de gravar os eventos da página.
- Grave cada evento pela mesma rotina do receptor, que deduplica pelo
id. - Um
401renova o token uma vez; um segundo401seguido indica credencial desativada e interrompe a leitura. - O feed alcança os últimos 30 dias. Um cursor parado há mais tempo que isso perde eventos.
- C#
- Node.js
- Python
- Java
public async Task SincronizarAsync(CancellationToken ct)
{
var after = await cursores.LerAsync("etech-feed") ?? 0;
var renovouToken = false;
while (true)
{
using var pedido = new HttpRequestMessage(HttpMethod.Get, $"/integration/v1/events?after={after}&limit=500");
pedido.Headers.Add("x-api-key", apiKey);
pedido.Headers.Authorization = new("Bearer", await tokens.ObterAsync(ct));
using var resposta = await http.SendAsync(pedido, ct);
if (resposta.StatusCode == HttpStatusCode.Unauthorized && !renovouToken)
{
tokens.Descartar();
renovouToken = true;
continue;
}
resposta.EnsureSuccessStatusCode();
renovouToken = false;
var pagina = await resposta.Content.ReadFromJsonAsync<PaginaDoFeed>(ct);
if (pagina is null || pagina.Events.Count == 0)
break;
foreach (var evento in pagina.Events)
await eventos.GravarSeNovoAsync(evento.GetRawText());
after = pagina.Next;
await cursores.GravarAsync("etech-feed", after);
}
}
sealed record PaginaDoFeed(
[property: JsonPropertyName("events")] List<JsonElement> Events,
[property: JsonPropertyName("next")] long Next);
import { obterToken, descartarToken } from './token.mjs';
const BASE_URL = 'https://vm-integration.staging.etechsa.com.br';
export async function sincronizar() {
let after = (await lerCursor('etech-feed')) ?? 0;
let renovouToken = false;
while (true) {
const resposta = await fetch(`${BASE_URL}/integration/v1/events?after=${after}&limit=500`, {
headers: {
'x-api-key': process.env.ETECH_API_KEY,
Authorization: `Bearer ${await obterToken()}`,
'User-Agent': 'MeuErp/1.0',
},
});
if (resposta.status === 401 && !renovouToken) {
descartarToken();
renovouToken = true;
continue;
}
if (!resposta.ok) throw new Error(`Feed recusado: ${resposta.status}`);
renovouToken = false;
const { events, next } = await resposta.json();
if (events.length === 0) break;
for (const evento of events) await gravarSeNovo(evento);
after = next;
await gravarCursor('etech-feed', after);
}
}
import os
import requests
from token_etech import obter_token, descartar_token
BASE_URL = "https://vm-integration.staging.etechsa.com.br"
def sincronizar() -> None:
after = ler_cursor("etech-feed") or 0
renovou_token = False
while True:
resposta = requests.get(
f"{BASE_URL}/integration/v1/events",
params={"after": after, "limit": 500},
headers={
"x-api-key": os.environ["ETECH_API_KEY"],
"Authorization": f"Bearer {obter_token()}",
"User-Agent": "MeuErp/1.0",
},
timeout=30,
)
if resposta.status_code == 401 and not renovou_token:
descartar_token()
renovou_token = True
continue
resposta.raise_for_status()
renovou_token = False
pagina = resposta.json()
if not pagina["events"]:
break
for evento in pagina["events"]:
gravar_se_novo(evento)
after = pagina["next"]
gravar_cursor("etech-feed", after)
public void sincronizar() throws Exception {
long after = cursores.ler("etech-feed").orElse(0L);
boolean renovouToken = false;
while (true) {
HttpRequest pedido = HttpRequest.newBuilder(
URI.create(BASE_URL + "/integration/v1/events?after=" + after + "&limit=500"))
.header("x-api-key", System.getenv("ETECH_API_KEY"))
.header("Authorization", "Bearer " + tokens.obter())
.header("User-Agent", "MeuErp/1.0")
.GET()
.build();
HttpResponse<String> resposta = http.send(pedido, HttpResponse.BodyHandlers.ofString());
if (resposta.statusCode() == 401 && !renovouToken) {
tokens.descartar();
renovouToken = true;
continue;
}
if (resposta.statusCode() != 200) throw new IllegalStateException("Feed recusado: " + resposta.statusCode());
renovouToken = false;
JsonNode pagina = json.readTree(resposta.body());
JsonNode lista = pagina.get("events");
if (lista.isEmpty()) break;
for (JsonNode evento : lista) eventos.gravarSeNovo(evento.toString());
after = pagina.get("next").asLong();
cursores.gravar("etech-feed", after);
}
}