Guides
How to receive and verify webhook events
Webhooks notify your server in real-time when events occur in SmartMCA.
POST request to your URL{
"eventId": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"eventType": "deal.funded",
"createdAt": "2026-03-04T12:00:00.000Z",
"data": {
"id": "clx...",
"dealId": "N-1001-DEM-A01",
"merchantName": "Acme Coffee",
"fundedAmount": 10000,
"status": "active"
}
}
| Header | Description |
|---|---|
X-SmartMCA-Signature | sha256={hmac} — HMAC-SHA256 of timestamp.body |
X-SmartMCA-Timestamp | Unix timestamp (seconds) when the event was sent |
X-SmartMCA-Event-Id | Unique event ID for deduplication |
Content-Type | application/json |
Every webhook includes an HMAC-SHA256 signature. The signature is computed over {timestamp}.{body} — the timestamp header value, a literal dot, and the raw request body — signed with your webhook secret.
Always verify signatures to ensure the request came from SmartMCA and wasn’t tampered with.
import crypto from 'crypto';
function verifyWebhook(
rawBody: string,
signatureHeader: string,
timestampHeader: string,
secret: string,
): boolean {
// 1. Reconstruct the signed payload
const signedPayload = `${timestampHeader}.${rawBody}`;
// 2. Compute expected signature
const expected = `sha256=${crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex')}`;
// 3. Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected),
);
}
// Express example
app.post('/webhooks/smartmca', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-smartmca-signature'] as string;
const timestamp = req.headers['x-smartmca-timestamp'] as string;
const body = req.body.toString();
if (!verifyWebhook(body, signature, timestamp, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(body);
console.log(`Received ${event.eventType}: ${event.eventId}`);
// Process asynchronously, respond immediately
res.status(200).send('OK');
});
import hmac
import hashlib
def verify_webhook(raw_body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
"""Verify SmartMCA webhook signature."""
signed_payload = f"{timestamp_header}.{raw_body.decode('utf-8')}"
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
signed_payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(signature_header, expected)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route("/webhooks/smartmca", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-SmartMCA-Signature", "")
timestamp = request.headers.get("X-SmartMCA-Timestamp", "")
if not verify_webhook(request.data, signature, timestamp, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
print(f"Received {event['eventType']}: {event['eventId']}")
return "OK", 200
using System.Security.Cryptography;
using System.Text;
public static bool VerifyWebhook(string rawBody, string signatureHeader, string timestampHeader, string secret)
{
var signedPayload = $"{timestampHeader}.{rawBody}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedPayload));
var expected = $"sha256={Convert.ToHexString(hash).ToLowerInvariant()}";
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(signatureHeader),
Encoding.UTF8.GetBytes(expected));
}
// ASP.NET Core example
[HttpPost("/webhooks/smartmca")]
public async Task<IActionResult> HandleWebhook()
{
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
var signature = Request.Headers["X-SmartMCA-Signature"].ToString();
var timestamp = Request.Headers["X-SmartMCA-Timestamp"].ToString();
if (!VerifyWebhook(body, signature, timestamp, _webhookSecret))
return Unauthorized();
var evt = JsonSerializer.Deserialize<WebhookEvent>(body);
_logger.LogInformation("Received {EventType}: {EventId}", evt.EventType, evt.EventId);
return Ok();
}
# Verify a webhook signature manually
TIMESTAMP="1709553600"
BODY='{"eventId":"evt_abc","eventType":"deal.funded","createdAt":"2026-03-04T12:00:00Z","data":{}}'
SECRET="whsec_your_secret_here"
EXPECTED=$(echo -n "${TIMESTAMP}.${BODY}" | openssl dgst -sha256 -hmac "${SECRET}" | awk '{print $2}')
echo "sha256=${EXPECTED}"
# Compare with the X-SmartMCA-Signature header value
2xx status code within 10 seconds to acknowledge receipt| Attempt | Delay |
|---|---|
| 1st retry | 5 minutes |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours |
| 4th retry | 12 hours |
After 4 retries (5 total attempts), the subscription is automatically paused. Check the delivery logs endpoint to diagnose failures.
X-SmartMCA-Timestamp and raw body.eventId for idempotent processing. The same event may be delivered more than once.GET /webhooks/{id}/deliveries to check for failures.