- Publicado em
Processando Webhooks de Validação Facial com C# e Entity Framework
- Autores
- Nome
- Maicon Oliveira
O Cenário
Em ecossistemas fintech, validações assíncronas são padrão. Você envia a biometria do usuário para análise e precisa esperar um callback. Este artigo demonstra como arquitetar uma solução robusta em .NET para receber esses webhooks, atualizar dados sensíveis (Email, Telefone, Conta Bancária) e expor um endpoint de consulta de status.
Vamos seguir rigorosamente os princípios de Clean Architecture, separando responsabilidades em Models, DTOs, Services e Controllers.
🏗️ 1. Domain Models & DTOs
Primeiro, definimos o que esperamos receber do provedor externo e como é a nossa entidade interna.
Request DTO (Webhook Payload)
O payload do webhook geralmente contém um ID único da transação e o status da validação.
// Dtos/FacialValidationWebhookDto.cs
public class FacialValidationWebhookDto
{
public Guid TransactionFaceId { get; set; }
public string Status { get; set; } // "Approved", "Rejected", "AwaitingWebhook"
public string? NewEmail { get; set; }
public string? NewPhoneNumber { get; set; }
public BankAccountDto? NewBankAccount { get; set; }
}
public class BankAccountDto
{
public string Branch { get; set; }
public string AccountNumber { get; set; }
public string BankCode { get; set; }
}
Entity Model
Nossa entidade de banco de dados que representa o processo de validação do cliente.
// Models/CustomerValidation.cs
public class CustomerValidation
{
public Guid Id { get; set; }
public Guid CustomerId { get; set; }
public Guid TransactionFaceId { get; set; } // External reference
public string Status { get; set; } // "Pending", "Approved", "Rejected"
public DateTime UpdatedAt { get; set; }
// Navigation property to the actual Customer
public virtual Customer Customer { get; set; }
}
⚙️ 2. The Service Layer
A lógica de negócio vive aqui. Precisamos processar o webhook (escrita) e verificar o status (leitura).
Interface
// Services/IFacialValidationService.cs
public interface IFacialValidationService
{
Task ProcessWebhookAsync(FacialValidationWebhookDto dto);
Task<string> GetValidationStatusAsync(Guid transactionFaceId);
}
Implementation (Entity Framework)
// Services/FacialValidationService.cs
public class FacialValidationService : IFacialValidationService
{
private readonly AppDbContext _context;
private readonly ILogger<FacialValidationService> _logger;
public FacialValidationService(AppDbContext context, ILogger<FacialValidationService> logger)
{
_context = context;
_logger = logger;
}
public async Task ProcessWebhookAsync(FacialValidationWebhookDto dto)
{
// 1. Find the validation record
var validation = await _context.CustomerValidations
.Include(v => v.Customer)
.FirstOrDefaultAsync(v => v.TransactionFaceId == dto.TransactionFaceId);
if (validation == null)
{
_logger.LogError($"Transaction {dto.TransactionFaceId} not found.");
return;
}
// 2. Update status
validation.Status = dto.Status;
validation.UpdatedAt = DateTime.UtcNow;
// 3. If Approved, update sensitive data
if (dto.Status == "Approved")
{
if (dto.NewEmail != null)
validation.Customer.Email = dto.NewEmail;
if (dto.NewPhoneNumber != null)
validation.Customer.PhoneNumber = dto.NewPhoneNumber;
if (dto.NewBankAccount != null)
{
validation.Customer.BankBranch = dto.NewBankAccount.Branch;
validation.Customer.BankAccount = dto.NewBankAccount.AccountNumber;
validation.Customer.BankCode = dto.NewBankAccount.BankCode;
}
}
// 4. Save changes (EF Core updates both tables in a transaction)
await _context.SaveChangesAsync();
}
public async Task<string> GetValidationStatusAsync(Guid transactionFaceId)
{
var status = await _context.CustomerValidations
.Where(v => v.TransactionFaceId == transactionFaceId)
.Select(v => v.Status)
.FirstOrDefaultAsync();
return status ?? "NotFound";
}
}
🎮 3. The Controller API
Com a lógica desacoplada, nosso Controller fica limpo e focado apenas nas preocupações HTTP.
// Controllers/FacialValidationController.cs
[ApiController]
[Route("api/v1/facial-validation")]
public class FacialValidationController : ControllerBase
{
private readonly IFacialValidationService _service;
public FacialValidationController(IFacialValidationService service)
{
_service = service;
}
// Endpoint 1: The Webhook Receiver
[HttpPost("webhook")]
public async Task<IActionResult> ReceiveWebhook([FromBody] FacialValidationWebhookDto payload)
{
if (payload == null) return BadRequest("Invalid payload");
// Fire-and-forget or await depending on requirement.
// Usually, webhooks expect a 200 OK quickly.
await _service.ProcessWebhookAsync(payload);
return Ok(new { message = "Webhook received and processed" });
}
// Endpoint 2: Status Polling
[HttpGet("status/{transactionFaceId}")]
public async Task<IActionResult> GetStatus(Guid transactionFaceId)
{
var status = await _service.GetValidationStatusAsync(transactionFaceId);
if (status == "NotFound") return NotFound();
return Ok(new { transactionId = transactionFaceId, status = status });
}
}
Conclusão
Ao usar Entity Framework, ganhamos o benefício do gerenciamento automático de transações — a atualização do registro de Validação e do Cliente acontece atomicamente quando SaveChangesAsync() é chamado.
Esta arquitetura traz clareza:
- Controllers apenas roteiam o tráfego.
- Services detêm as regras de domínio (ex: só atualizar dados se "Approved").
- DTOs protegem nossa estrutura interna de banco de dados contratos externos.