Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ jspm_packages/
openspec/
nfeio-docs
client-ruby
client-php
# ----------------------------------------------------------------------------
# Build Outputs
# ----------------------------------------------------------------------------
Expand Down
48 changes: 47 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,50 @@ Todas as mudanças notáveis neste projeto serão documentadas neste arquivo.
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/),
e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).

## [5.1.0] - 2026-07-03

> Correção do contrato de webhooks contra a API real, provado por sonda ao vivo
> (2026-07-02/03, três contas). O contrato correto sempre esteve nos specs oficiais
> (`openapi/spec/nf-servico-v1.yaml` e equivalentes) — o recurso manuscrito havia
> divergido deles.

### Corrigido

- **`createAccountWebhook` funcionava 0% das vezes**: a API exige o request
envelopado em `{ "webHook": {...} }` (sem ele responde
`400 "missing required properties including: 'webHook'"`) e devolve a resposta
também envelopada. O SDK agora envelopa o request (create/update) e desembrulha
as respostas (create/retrieve/update), com fallback defensivo para corpo cru.
- `listAccountWebhooks`/`retrieveAccountWebhook`/`updateAccountWebhook` agora
tipados com o shape real do recurso (ver `AccountWebhook` abaixo).

### Adicionado

- Tipo **`AccountWebhook`** com o shape real da API: `uri`, `contentType`,
`secret` (32–64 caracteres, ecoado no create e omitido nas leituras), `filters`,
`insecureSsl`, `headers`, `properties`, `status`, `createdOn`, `modifiedOn`.
Nota: o spec declara `contentType`/`status` como enums inteiros, mas a API
serializa strings (`"json"`, `"Active"`) — o tipo segue o fio real.
- Tipo **`WebhookEventType`** (união aberta) com os 46 event types reais de
`GET /v2/webhooks/eventTypes` (`service_invoice.issued_successfully`, etc.).
- Teste de alinhamento (`tests/types/account-webhook-alignment.test-d.ts`)
amarrando o `AccountWebhook` ao schema gerado do spec oficial — um sync de spec
que mude o contrato de webhooks quebra o `npm run test:types` em vez de driftar.
- JSDoc do `createAccountWebhook` documenta a verificação de URI na criação
(a NFE.io faz um ping e exige resposta 2xx).
- JSDoc do `updateAccountWebhook` documenta que o `PUT` é substituição integral
(confirmado ao vivo em 2026-07-03): campos omitidos voltam ao padrão — update
sem `status` **desativa o webhook**. Envie o objeto completo (parta do retrieve).

### Deprecado

- Métodos company-scoped de webhooks (`list`, `create`, `retrieve`, `update`,
`delete`, `test` sobre `/v1/companies/{id}/webhooks`): a rota retorna **404**
na API atual (confirmado em três contas, 2026-07-02/03). Use os equivalentes
account-scoped. O comportamento não mudou; remoção fica para a próxima major.
- Tipos `Webhook` e `WebhookEvent`: shapes que a API real rejeita. Use
`AccountWebhook` e `WebhookEventType`.

## [5.0.0] - 2026-06-30

> Primeira release de **funcionalidades** desde a v3 — a v4 foi apenas o bump de runtime
Expand Down Expand Up @@ -793,6 +837,8 @@ SDK JavaScript legado com API baseada em callbacks.

## Links

[Unreleased]: https://github.com/nfe/client-nodejs/compare/v3.0.0...HEAD
[Unreleased]: https://github.com/nfe/client-nodejs/compare/v5.1.0...HEAD
[5.1.0]: https://github.com/nfe/client-nodejs/compare/v5.0.0...v5.1.0
[5.0.0]: https://github.com/nfe/client-nodejs/releases/tag/v5.0.0
[3.0.0]: https://github.com/nfe/client-nodejs/releases/tag/v3.0.0
[2.0.0]: https://github.com/nfe/client-nodejs/releases/tag/v2.0.0
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,22 +323,24 @@ const pessoa = await nfe.naturalPeople.findByTaxNumber(empresaId, '12345678901')

#### 🔗 Webhooks (`nfe.webhooks`)

Gerenciar configurações de webhook:
Gerenciar configurações de webhook. Webhooks são gerenciados **por conta**
(`/v2/webhooks`) — os métodos por empresa estão deprecated (a rota retorna 404):

```typescript
// Criar webhook
const webhook = await nfe.webhooks.create(empresaId, {
url: 'https://meuapp.com.br/webhooks/nfe',
events: ['invoice.issued', 'invoice.cancelled'],
active: true
// Criar webhook — a URI precisa responder 2xx já na criação (ping de verificação)
const webhook = await nfe.webhooks.createAccountWebhook({
uri: 'https://meuapp.com.br/webhooks/nfe',
contentType: 'json',
secret: 'um-segredo-de-32-a-64-caracteres-aqui',
filters: ['service_invoice.issued_successfully', 'service_invoice.cancelled_successfully']
});

// Listar webhooks
const webhooks = await nfe.webhooks.list(empresaId);
// Listar webhooks da conta
const webhooks = await nfe.webhooks.listAccountWebhooks();

// Atualizar webhook
await nfe.webhooks.update(empresaId, webhookId, {
events: ['invoice.issued']
await nfe.webhooks.updateAccountWebhook(webhookId, {
filters: ['service_invoice.issued_successfully']
});

// Validar assinatura do webhook
Expand Down
67 changes: 43 additions & 24 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1425,54 +1425,72 @@ const person = await nfe.naturalPeople.findByTaxNumber('company-id', '1234567890

**Resource:** `nfe.webhooks`

Webhook configuration and management.
Webhook configuration and management. Webhooks are **account-scoped**
(`/v2/webhooks`) — the methods take no `companyId`.

#### `create(data: Partial<Webhook>): Promise<Webhook>`
> ⚠ **Deprecated:** the company-scoped methods (`create/list/retrieve/update/delete/test(companyId, ...)`)
> target `/v1/companies/{id}/webhooks`, which returns **404** on the current API.
> Use the account-scoped methods below.

Create a webhook.
#### `createAccountWebhook(data: AccountWebhook): Promise<AccountWebhook>`

Create an account webhook. NFE.io **verifies the `uri` at creation time** with a
ping that must receive a 2xx response — the endpoint must already be live. The
`secret` must be 32–64 characters (echoed in the create response, omitted on reads).

```typescript
const webhook = await nfe.webhooks.create({
url: 'https://example.com/webhook',
events: ['invoice.issued', 'invoice.cancelled'],
secret: 'webhook-secret'
const webhook = await nfe.webhooks.createAccountWebhook({
uri: 'https://example.com/webhook', // must answer 2xx at creation time
contentType: 'json',
secret: 'a-secret-with-32-to-64-characters-x',
filters: ['service_invoice.issued_successfully', 'service_invoice.cancelled_successfully']
});
```

#### `list(options?: PaginationOptions): Promise<ListResponse<Webhook>>`
#### `listAccountWebhooks(): Promise<ListResponse<AccountWebhook>>`

List all webhooks.
List all account webhooks.

```typescript
const webhooks = await nfe.webhooks.list();
const webhooks = await nfe.webhooks.listAccountWebhooks();
```

#### `retrieve(webhookId: string): Promise<Webhook>`
#### `retrieveAccountWebhook(webhookId: string): Promise<AccountWebhook>`

Get a specific webhook.

```typescript
const webhook = await nfe.webhooks.retrieve('webhook-id');
const webhook = await nfe.webhooks.retrieveAccountWebhook('webhook-id');
```

#### `update(webhookId: string, data: Partial<Webhook>): Promise<Webhook>`
#### `updateAccountWebhook(webhookId: string, data: Partial<AccountWebhook>): Promise<AccountWebhook>`

Update webhook configuration.

> ⚠ `PUT` is a **full replacement**: omitted fields reset to their defaults — an
> update without `status` **deactivates the webhook** (`status` becomes
> `"Inactive"`). Send the complete object, e.g. starting from a retrieve:

```typescript
const updated = await nfe.webhooks.update('webhook-id', {
events: ['invoice.issued', 'invoice.cancelled', 'invoice.error']
const current = await nfe.webhooks.retrieveAccountWebhook('webhook-id');
const updated = await nfe.webhooks.updateAccountWebhook('webhook-id', {
...current,
filters: ['service_invoice.issued_successfully', 'service_invoice.issued_error']
});
```

#### `delete(webhookId: string): Promise<void>`
#### `deleteAccountWebhook(webhookId: string): Promise<void>`

Delete a webhook.

```typescript
await nfe.webhooks.delete('webhook-id');
await nfe.webhooks.deleteAccountWebhook('webhook-id');
```

#### `deleteAllAccountWebhooks(): Promise<void>`

⚠ **Destructive:** removes **all** webhooks on the account.

#### `validateSignature(payload: Buffer | string, signature: string | string[] | undefined, secret: string): boolean`

Validate the signature on a webhook delivery from NFE.io.
Expand Down Expand Up @@ -1503,21 +1521,22 @@ app.post(
);
```

#### `test(webhookId: string): Promise<void>`
#### `pingAccountWebhook(webhookId: string): Promise<void>`

Test webhook delivery.
Trigger a test ping for a webhook.

```typescript
await nfe.webhooks.test('webhook-id');
await nfe.webhooks.pingAccountWebhook('webhook-id');
```

#### `getAvailableEvents(): Promise<WebhookEvent[]>`
#### `fetchEventTypes(): Promise<WebhookEventType[]>`

Get list of available webhook event types.
Fetch the live list of available webhook event types from the API (prefer this
over the deprecated hardcoded `getAvailableEvents()`).

```typescript
const events = await nfe.webhooks.getAvailableEvents();
// ['invoice.issued', 'invoice.cancelled', ...]
const events = await nfe.webhooks.fetchEventTypes();
// ['service_invoice.issued_successfully', 'service_invoice.cancelled_successfully', ...]
```

---
Expand Down
35 changes: 22 additions & 13 deletions docs/recursos/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Webhooks (recurso)
sidebar_label: Webhooks
sidebar_position: 9
slug: recurso-webhooks
description: Métodos de webhooks por empresa e por conta, verificação de assinatura e lista de eventos ao vivo com nfe.webhooks.
description: Métodos de webhooks por conta, verificação de assinatura e lista de eventos ao vivo com nfe.webhooks.
---

# Webhooks (recurso)
Expand All @@ -12,15 +12,9 @@ description: Métodos de webhooks por empresa e por conta, verificação de assi
entrega. Para o guia conceitual (assinatura HMAC, `express.raw`), veja
[Webhooks](../webhooks.md).

## Métodos — por empresa (`/companies/{id}/webhooks`)

| Método | Descrição |
|---|---|
| `list(companyId)` | Lista os webhooks da empresa. |
| `create(companyId, data)` | Cria um webhook. |
| `retrieve(companyId, webhookId)` / `update(...)` / `delete(...)` | CRUD. |
| `test(companyId, webhookId)` | Dispara um teste. |
| `validateSignature(payload, signature, secret)` | Valida a assinatura HMAC-SHA1 (`x-hub-signature`). |
Webhooks são gerenciados **por conta** (`/v2/webhooks`). Os métodos por empresa
(`list/create/retrieve/update/delete/test(companyId, ...)`) estão **deprecated**:
a rota `/v1/companies/{id}/webhooks` retorna 404 na API atual.

## Métodos — por conta (`/v2/webhooks`, sem `companyId`)

Expand All @@ -32,19 +26,34 @@ entrega. Para o guia conceitual (assinatura HMAC, `express.raw`), veja
| `pingAccountWebhook(id)` | Dispara um ping de teste. |
| `deleteAllAccountWebhooks()` | ⚠️ Remove **todos** os webhooks da conta. |
| `fetchEventTypes()` | Lista de tipos de evento **ao vivo** (`string[]`). |
| `validateSignature(payload, signature, secret)` | Valida a assinatura HMAC-SHA1 (`x-hub-signature`). |

## Exemplo

```typescript
const eventTypes = await nfe.webhooks.fetchEventTypes();

const created = await nfe.webhooks.createAccountWebhook({
uri: 'https://seu-site.com/webhook',
events: eventTypes.slice(0, 2),
uri: 'https://seu-site.com/webhook', // precisa responder 2xx já na criação (ping)
contentType: 'json',
secret: 'um-segredo-de-32-a-64-caracteres-aqui',
filters: ['service_invoice.issued_successfully', 'service_invoice.cancelled_successfully'],
});
await nfe.webhooks.pingAccountWebhook(created.id);
if (created.id) await nfe.webhooks.pingAccountWebhook(created.id);
```

:::info Verificação na criação
Ao criar um webhook, a NFE.io faz um ping na `uri` e exige resposta **2xx** —
o endpoint precisa estar no ar antes do `createAccountWebhook`. O `secret`
(32–64 caracteres) é ecoado na resposta do create, mas omitido nas leituras.
:::

:::caution Update é substituição integral
`updateAccountWebhook` faz um `PUT`: campos omitidos voltam ao padrão — um
update sem `status` **desativa o webhook**. Parta do `retrieveAccountWebhook`
e envie o objeto completo.
:::

## Próximos passos

- [Webhooks (guia)](../webhooks.md)
Expand Down
20 changes: 13 additions & 7 deletions docs/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,23 @@ verificação falha. Use `express.raw()` e passe o `Buffer`.
A comparação é feita com `timingSafeEqual` (resistente a timing attacks) e
aceita a assinatura como `string` ou `string[]`.

## Webhooks por empresa vs por conta
## Webhooks são por conta

| Escopo | Acesso | Caminho |
|---|---|---|
| Empresa | `nfe.webhooks.list/create/retrieve/update/delete(companyId, ...)` | `/companies/{id}/webhooks` |
| Conta | `nfe.webhooks.listAccountWebhooks/createAccountWebhook/...` | `/v2/webhooks` (host-root) |

Métodos de conta (sem `companyId`): `listAccountWebhooks`, `createAccountWebhook`,
Webhooks são registrados e gerenciados **por conta** (`/v2/webhooks`), sem
`companyId`: `listAccountWebhooks`, `createAccountWebhook`,
`retrieveAccountWebhook`, `updateAccountWebhook`, `deleteAccountWebhook`,
`pingAccountWebhook` e `deleteAllAccountWebhooks` (⚠️ remove **todos**).

Na criação, a NFE.io **verifica a `uri`** com um ping que exige resposta 2xx —
o endpoint precisa estar no ar antes do `createAccountWebhook`. O `secret`
deve ter 32–64 caracteres.

:::caution Métodos por empresa deprecated
`nfe.webhooks.list/create/retrieve/update/delete/test(companyId, ...)` estão
**deprecated**: a rota `/v1/companies/{id}/webhooks` retorna 404 na API atual.
Use os métodos por conta acima.
:::

## Tipos de evento ao vivo

Prefira `fetchEventTypes()` (fonte da verdade é o servidor) ao invés da lista
Expand Down
27 changes: 14 additions & 13 deletions examples/all-resources-demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,22 +134,23 @@ const naturalPerson = await nfe.naturalPeople.create('company-id', {
console.log('\n5️⃣ WEBHOOKS - Notificações de Eventos');
console.log('─'.repeat(50));

console.log('Funcionalidades:');
console.log('✓ list(companyId) - Listar webhooks');
console.log('✓ create(companyId, data) - Criar webhook');
console.log('✓ retrieve(companyId, id) - Buscar webhook');
console.log('✓ update(companyId, id, data) - Atualizar');
console.log('✓ delete(companyId, id) - Deletar');
console.log('✓ test(companyId, id) - Testar webhook');
console.log('Funcionalidades (escopo CONTA — /v2/webhooks):');
console.log('✓ listAccountWebhooks() - Listar webhooks');
console.log('✓ createAccountWebhook(data) - Criar webhook');
console.log('✓ retrieveAccountWebhook(id) - Buscar webhook');
console.log('✓ updateAccountWebhook(id, data) - Atualizar');
console.log('✓ deleteAccountWebhook(id) - Deletar');
console.log('✓ pingAccountWebhook(id) - Testar webhook');
console.log('✓ validateSignature() - Validar assinatura');
console.log('✓ getAvailableEvents() - Eventos disponíveis\n');
console.log('✓ fetchEventTypes() - Eventos disponíveis (lista viva)\n');

console.log('Exemplo de criação:');
console.log('Exemplo de criação (escopo CONTA — a uri precisa responder 2xx na criação):');
console.log(`
const webhook = await nfe.webhooks.create('company-id', {
url: 'https://seu-site.com/webhook/nfe',
events: ['invoice.issued', 'invoice.cancelled'],
secret: 'sua-chave-secreta'
const webhook = await nfe.webhooks.createAccountWebhook({
uri: 'https://seu-site.com/webhook/nfe',
contentType: 'json',
secret: 'um-segredo-de-32-a-64-caracteres-aqui',
filters: ['service_invoice.issued_successfully', 'service_invoice.cancelled_successfully']
});
`);

Expand Down
16 changes: 9 additions & 7 deletions examples/jsdoc-intellisense-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,20 @@ async function demonstrateJSDoc() {
const envClient = createClientFromEnv('production');

// Example 7: Resource-specific operations with docs
// All webhook methods have comprehensive documentation
const webhook = await nfe.webhooks.create(companyId, {
url: 'https://example.com/webhook',
events: ['invoice.issued', 'invoice.cancelled'],
secret: 'webhook-secret'
// All webhook methods have comprehensive documentation.
// Webhooks are account-scoped (/v2/webhooks); the uri must answer 2xx at creation.
const webhook = await nfe.webhooks.createAccountWebhook({
uri: 'https://example.com/webhook',
contentType: 'json',
secret: 'um-segredo-de-32-a-64-caracteres-aqui',
filters: ['service_invoice.issued_successfully', 'service_invoice.cancelled_successfully'],
});

// Hover over "validateSignature" to see HMAC validation docs
const isValid = nfe.webhooks.validateSignature(
'{"event": "invoice.issued"}',
'{"event": "service_invoice.issued_successfully"}',
'signature-from-header',
'webhook-secret'
'um-segredo-de-32-a-64-caracteres-aqui'
);

// Example 8: Company operations with certificate upload
Expand Down
Loading
Loading