Xác thực Webhook
Khi có giao dịch phát sinh, Tingee gửi Webhook (IPN) đến endpoint của đối tác. SDK cung cấp method verifyWebhookSignature() giúp xác thực chữ ký một cách đơn giản và an toàn.
Tại sao cần xác thực?
Header x-signature đảm bảo request đến từ Tingee, không phải từ bên thứ ba giả mạo. Luôn xác thực trước khi xử lý giao dịch.
Cách sử dụng
- Node.js (Express)
- Node.js (NestJS)
- Java (Spring Boot)
- C# (ASP.NET Core)
- PHP (thuần)
- PHP (Laravel)
import express from 'express';
import { TingeeClient, isErrorResponse } from '@tingee/sdk-node';
const client = new TingeeClient({
secretKey: process.env.TINGEE_SECRET_KEY!,
clientId: process.env.TINGEE_CLIENT_ID!,
});
app.post('/webhook/tingee', express.json(), (req, res) => {
const result = client.verifyWebhookSignature(
req.headers['x-signature'] as string,
req.headers['x-request-timestamp'] as string,
req.body,
);
if (isErrorResponse(result)) {
return res.status(401).json({ error: result.message });
}
// <span class="status-badge ok" title="Bắt buộc">✓</span> Chữ ký hợp lệ — xử lý giao dịch
const { transactionCode, amount } = req.body;
// Cập nhật đơn hàng...
res.json({ received: true });
});
import { Controller, Post, Req, Body, UnauthorizedException } from '@nestjs/common';
import { TingeeClient, isErrorResponse } from '@tingee/sdk-node';
import { Request } from 'express';
@Controller()
export class WebhookController {
constructor(private readonly tingeeClient: TingeeClient) {}
@Post('webhook/tingee')
handleWebhook(@Req() req: Request, @Body() body: Record<string, any>) {
const result = this.tingeeClient.verifyWebhookSignature(
req.headers['x-signature'] as string,
req.headers['x-request-timestamp'] as string,
body,
);
if (isErrorResponse(result)) {
throw new UnauthorizedException(result.message);
}
// <span class="status-badge ok" title="Bắt buộc">✓</span> Xử lý giao dịch...
return { received: true };
}
}
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import vn.tingee.sdk.TingeeClient;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Map;
@RestController
public class WebhookController {
private final TingeeClient client;
public WebhookController(TingeeClient client) {
this.client = client;
}
@PostMapping("/webhook/tingee")
public ResponseEntity<?> handleWebhook(
HttpServletRequest req,
@RequestBody Map<String, Object> body) {
var result = client.verifyWebhookSignature(
req.getHeader("x-signature"),
req.getHeader("x-request-timestamp"),
body
);
if (!result.isError()) {
return ResponseEntity.status(401)
.body(Map.of("error", result.getMessage()));
}
// <span class="status-badge ok" title="Bắt buộc">✓</span> Chữ ký hợp lệ — xử lý giao dịch
return ResponseEntity.ok(Map.of("received", true));
}
}
using Microsoft.AspNetCore.Mvc;
using Tingee.Sdk.Client;
[ApiController]
public class WebhookController : ControllerBase
{
private readonly TingeeClient _client;
public WebhookController(TingeeClient client) => _client = client;
[HttpPost("/webhook/tingee")]
public async Task<IActionResult> HandleWebhook(
[FromBody] object body)
{
var signature = Request.Headers["x-signature"].ToString();
var timestamp = Request.Headers["x-request-timestamp"].ToString();
var result = _client.VerifyWebhookSignature(signature, timestamp, body);
if (!result.IsValid)
return Unauthorized(new { error = result.Message });
// <span class="status-badge ok" title="Bắt buộc">✓</span> Chữ ký hợp lệ — xử lý giao dịch
return Ok(new { received = true });
}
}
<?php
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_REQUEST_TIMESTAMP'] ?? '';
$body = json_decode(file_get_contents('php://input'), true);
$result = $client->verifyWebhookSignature($signature, $timestamp, $body);
if ($result->isError()) {
http_response_code(401);
echo json_encode(['error' => $result->message]);
exit;
}
// <span class="status-badge ok" title="Bắt buộc">✓</span> Chữ ký hợp lệ — xử lý giao dịch
echo json_encode(['received' => true]);
use Illuminate\Http\Request;
use Tingee\Sdk\TingeeClient;
Route::post('/webhook/tingee', function (Request $request, TingeeClient $client) {
$result = $client->verifyWebhookSignature(
$request->header('x-signature'),
$request->header('x-request-timestamp'),
$request->all()
);
if ($result->isError()) {
abort(401, $result->message);
}
// <span class="status-badge ok" title="Bắt buộc">✓</span> Xử lý giao dịch...
return response()->json(['received' => true]);
});
Chính sách Retry của Tingee
Nếu endpoint của đối tác không phản hồi trong vòng 10 giây (timeout), Tingee sẽ tự động gửi lại webhook.
Idempotency
Luôn dùng transactionId hoặc orderId để kiểm tra xem đơn hàng đã xử lý chưa, tránh trùng lặp khi nhận retry.
Xem thêm
- Tổng quan Webhook (IPN) — Luồng hoạt động, danh mục sự kiện
- Webhook Payment Callback — Cấu trúc payload chi tiết