Os webhooks são notificações automáticas enviadas pela Nuvende para informar os parceiros sobre mudanças no status das solicitações de autorização. Este mecanismo permite que o parceiro seja notificado em tempo real sobre as decisões dos clientes.
Os webhooks funcionam como um sistema de notificação push:
Quando o cliente aprova uma solicitação, o webhook contém todas as informações necessárias para integração:
{
"auth_request_id": "8e9a8f81-aa432-4608-b4a7-e223c3213231",
"status": "approved",
"scopes": [
"cob.read",
"cob.write",
"pix.write"
],
"client_id": "ffcfb2ed-d669-4e33-9c4d-e223c3213231",
"client_secret": "nFNTATLHFp1hGAPZNU0JKJDGQFSCDAS",
"account_id": "314eae12-19b7-4845-b115-e223c3213231",
"account": "0000001",
"account_vd": "1",
"agency": "0001",
"agency_vd": "9",
"document": "04169999999",
"dict_keys": [
"22598a0a-b222-4329-8603-e223c3213231"
],
"decision_at": "2025-01-15T14:01:39-03:00",
"created_at": "2025-01-15T13:58:30-03:00",
"updated_at": "2025-01-15T14:01:39-03:00"
}
| Campo | Tipo | Descrição |
|---|---|---|
auth_request_id | string | ID da solicitação de autorização |
status | string | Status da solicitação ("approved") |
scopes | array | Escopos aprovados pelo cliente |
client_id | string | Credencial para integração |
client_secret | string | Chave secreta para autenticação |
account_id | string | ID da conta do cliente |
account | string | Número da conta |
account_vd | string | Dígito verificador da conta |
agency | string | Número da agência |
agency_vd | string | Dígito verificador da agência |
document | string | CPF/CNPJ do cliente |
dict_keys | array | Chaves de dicionário para Pix |
decision_at | string | Data/hora da decisão |
created_at | string | Data de criação da solicitação |
updated_at | string | Data da última atualização |
Quando o cliente rejeita uma solicitação:
{
"auth_request_id": "4008bfa4-6373-4301-a5bb-e223c3213231",
"response": "rejected",
"message": "User has denied your request",
"decision_at": "2025-01-15T14:24:14-03:00"
}
| Campo | Tipo | Descrição |
|---|---|---|
auth_request_id | string | ID da solicitação de autorização |
response | string | Resposta do cliente ("rejected") |
message | string | Mensagem explicativa |
decision_at | string | Data/hora da decisão |
A URL do webhook deve ser configurada ao criar a solicitação de autorização:
curl --location 'https://api-h.nuvende.com.br/api/v2/partners/requests' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {access_token}' \
--data '{
"application_id": "abnks92-cb57-4ee3-a997-e223c3213231",
"name": "João Silva",
"document": "12345678901",
"email": "[email protected]",
"phone": "11999999999",
"origin": "partner",
"scopes": ["cob.read", "cob.write"],
"webhook_url": "https://meusite.com/webhook/autorizacao",
"metadata": ["Solicitação via app"]
}'
const express = require('express');
const app = express();
app.use(express.json());
// Endpoint para receber webhooks
app.post('/webhook/autorizacao', (req, res) => {
try {
const webhookData = req.body;
console.log('Webhook recebido:', webhookData);
// Processar baseado no status
if (webhookData.status === 'approved') {
handleApprovedRequest(webhookData);
} else if (webhookData.response === 'rejected') {
handleRejectedRequest(webhookData);
}
// Sempre retornar 200 para confirmar recebimento
res.status(200).json({ received: true });
} catch (error) {
console.error('Erro ao processar webhook:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
const handleApprovedRequest = (data) => {
console.log('Solicitação aprovada:', data.auth_request_id);
// Salvar credenciais de forma segura
const credentials = {
client_id: data.client_id,
client_secret: data.client_secret,
account_id: data.account_id,
scopes: data.scopes
};
// Implementar lógica de integração
initializeIntegration(credentials);
};
const handleRejectedRequest = (data) => {
console.log('Solicitação rejeitada:', data.auth_request_id);
console.log('Motivo:', data.message);
// Implementar lógica de notificação ao usuário
notifyUserRejection(data);
};
app.listen(3000, () => {
console.log('Servidor webhook rodando na porta 3000');
});
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
@app.route('/webhook/autorizacao', methods=['POST'])
def webhook_handler():
try:
webhook_data = request.get_json()
print(f'Webhook recebido: {webhook_data}')
# Processar baseado no status
if webhook_data.get('status') == 'approved':
handle_approved_request(webhook_data)
elif webhook_data.get('response') == 'rejected':
handle_rejected_request(webhook_data)
# Sempre retornar 200 para confirmar recebimento
return jsonify({'received': True}), 200
except Exception as e:
print(f'Erro ao processar webhook: {e}')
return jsonify({'error': 'Internal server error'}), 500
def handle_approved_request(data):
print(f'Solicitação aprovada: {data["auth_request_id"]}')
# Salvar credenciais de forma segura
credentials = {
'client_id': data['client_id'],
'client_secret': data['client_secret'],
'account_id': data['account_id'],
'scopes': data['scopes']
}
# Implementar lógica de integração
initialize_integration(credentials)
def handle_rejected_request(data):
print(f'Solicitação rejeitada: {data["auth_request_id"]}')
print(f'Motivo: {data["message"]}')
# Implementar lógica de notificação ao usuário
notify_user_rejection(data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
Recomenda-se implementar validação de segurança:
const crypto = require('crypto');
const validateWebhook = (payload, signature, secret) => {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
};
// Uso no endpoint
app.post('/webhook/autorizacao', (req, res) => {
const signature = req.headers['x-nuvende-signature'];
const payload = JSON.stringify(req.body);
if (!validateWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Processar webhook...
});
| Código | Descrição | Ação do Sistema |
|---|---|---|
200 | Sucesso | Webhook processado com sucesso |
400 | Bad Request | Dados inválidos |
401 | Unauthorized | Assinatura inválida |
500 | Server Error | Sistema tentará reenviar |
O sistema Nuvende implementa reenvio automático:
const logWebhook = (data, status) => {
console.log({
timestamp: new Date().toISOString(),
auth_request_id: data.auth_request_id,
status: status,
client_ip: req.ip,
user_agent: req.headers['user-agent']
});
};
// Uso
app.post('/webhook/autorizacao', (req, res) => {
const webhookData = req.body;
try {
// Processar webhook
processWebhook(webhookData);
logWebhook(webhookData, 'success');
res.status(200).json({ received: true });
} catch (error) {
logWebhook(webhookData, 'error');
res.status(500).json({ error: 'Processing failed' });
}
});
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Middleware de validação
const validateWebhook = (req, res, next) => {
const signature = req.headers['x-nuvende-signature'];
const payload = JSON.stringify(req.body);
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
};
// Endpoint principal
app.post('/webhook/autorizacao', validateWebhook, (req, res) => {
const webhookData = req.body;
try {
console.log('Webhook recebido:', {
auth_request_id: webhookData.auth_request_id,
status: webhookData.status || webhookData.response,
timestamp: new Date().toISOString()
});
// Processar baseado no tipo
if (webhookData.status === 'approved') {
handleApprovedRequest(webhookData);
} else if (webhookData.response === 'rejected') {
handleRejectedRequest(webhookData);
}
res.status(200).json({ received: true });
} catch (error) {
console.error('Erro ao processar webhook:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
const handleApprovedRequest = async (data) => {
try {
// Salvar credenciais
await saveCredentials({
auth_request_id: data.auth_request_id,
client_id: data.client_id,
client_secret: data.client_secret,
account_id: data.account_id,
scopes: data.scopes,
account_info: {
account: data.account,
account_vd: data.account_vd,
agency: data.agency,
agency_vd: data.agency_vd
}
});
// Inicializar integração
await initializeIntegration(data);
console.log('Integração inicializada para:', data.auth_request_id);
} catch (error) {
console.error('Erro ao processar aprovação:', error);
throw error;
}
};
const handleRejectedRequest = (data) => {
console.log('Solicitação rejeitada:', {
auth_request_id: data.auth_request_id,
message: data.message,
decision_at: data.decision_at
});
// Notificar usuário sobre rejeição
notifyUserRejection(data);
};
app.listen(3000, () => {
console.log('Servidor webhook rodando na porta 3000');
});
Após configurar webhooks: