Webhook’lar
Solidus entegrasyonunuzda olaylar gerçekleştiğinde gerçek zamanlı bildirimler alın. Webhook’lar, bir doğrulama tamamlandığında, bir kimlik bilgisi düzenlendiğinde veya başka olaylar meydana geldiğinde uç noktanıza HTTP POST istekleri teslim eder.
Temel URL
https://verify.solidus.network/v1API anahtarınızla istekleri kimlik doğrulayın:
Authorization: Bearer YOUR_API_KEY1. Bir Webhook Uç Noktası Oluşturun
Webhook olaylarını almak için bir URL kaydedin.
const response = await fetch('https://verify.solidus.network/v1/webhooks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({
url: 'https://myapp.com/webhooks/solidus',
events: [
'verification.completed',
'verification.failed',
'verification.expired',
'credential.issued',
'credential.revoked',
],
description: 'Production webhook for KYC events',
}),
})
const webhook = await response.json()Yanıt:
{
"id": "wh_3nK8mR2pQ5",
"url": "https://myapp.com/webhooks/solidus",
"events": [
"verification.completed",
"verification.failed",
"verification.expired",
"credential.issued",
"credential.revoked"
],
"description": "Production webhook for KYC events",
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"status": "active",
"createdAt": "2026-05-07T12:00:00Z"
}secret değerini kaydedin. Webhook imzalarını doğrulamak için onu kullanacaksınız. Sır, yalnızca
oluşturma sırasında bir kez döndürülür.
2. Desteklenen Olaylar
| Event | Trigger |
|---|---|
verification.completed | KYC doğrulaması geçti, kimlik bilgisi düzenlendi |
verification.failed | KYC doğrulaması başarısız oldu (belge sorunları, yüz uyuşmazlığı) |
verification.expired | Doğrulama oturumu tamamlanmadan önce sona erdi |
credential.issued | Yeni bir Doğrulanabilir Kimlik Bilgisi düzenlendi |
credential.revoked | Mevcut bir kimlik bilgisi iptal edildi |
3. Webhook Yük (Payload) Biçimi
Her webhook teslimatı, aşağıdaki yapıya sahip bir JSON yükü gönderir:
{
"id": "evt_8nR3kL5mQ2xY",
"type": "verification.completed",
"createdAt": "2026-05-07T12:05:30Z",
"data": {
"verificationId": "ver_2xK9mP4qR7nL",
"status": "completed",
"outcome": "pass",
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"level": 1,
"credentialId": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Olay Türüne Göre Yük
verification.completed
{
"id": "evt_8nR3kL5mQ2xY",
"type": "verification.completed",
"createdAt": "2026-05-07T12:05:30Z",
"data": {
"verificationId": "ver_2xK9mP4qR7nL",
"status": "completed",
"outcome": "pass",
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"level": 1,
"credentialId": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}verification.failed
{
"id": "evt_9pQ4rM6sT3wZ",
"type": "verification.failed",
"createdAt": "2026-05-07T12:05:30Z",
"data": {
"verificationId": "ver_2xK9mP4qR7nL",
"status": "failed",
"outcome": "fail",
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"reason": "face_mismatch",
"message": "The selfie does not match the photo on the identity document."
}
}credential.issued
{
"id": "evt_5kN2jH8bF7mR",
"type": "credential.issued",
"createdAt": "2026-05-07T12:05:30Z",
"data": {
"credentialId": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": ["VerifiableCredential", "KYCCredential"],
"issuer": "did:solidus:testnet:verify-service",
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"issuanceDate": "2026-05-07T12:05:30Z",
"expirationDate": "2027-05-07T12:05:30Z",
"txHash": "0x7a3b9c2d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b"
}
}credential.revoked
{
"id": "evt_4mL9gK3cE6nP",
"type": "credential.revoked",
"createdAt": "2026-05-07T14:20:00Z",
"data": {
"credentialId": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"revokedBy": "did:solidus:testnet:verify-service",
"reason": "Information no longer accurate",
"txHash": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"
}
}4. İmza Doğrulama
Her webhook isteği, X-Solidus-Signature başlığında bir imza içerir. İsteğin Solidus’tan geldiğini
doğrulamak için her zaman bu imzayı kontrol edin.
İmza, webhook sırrınızı anahtar olarak kullanan, ham istek gövdesinin bir HMAC-SHA256 hex özetidir.
import crypto from 'crypto'
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
}İstek ayrıca bir Unix zaman damgasıyla bir X-Solidus-Timestamp başlığı içerir. Yeniden oynatma
saldırılarını önlemek için zaman damgası 5 dakikadan daha eski olan istekleri reddedin.
5. Tam Express Webhook İşleyicisi
Solidus webhook’larını alan, doğrulayan ve işleyen üretime hazır bir Express işleyicisi.
import express from 'express'
import crypto from 'crypto'
const app = express()
const WEBHOOK_SECRET = process.env.SOLIDUS_WEBHOOK_SECRET!
// Use raw body for signature verification
app.post(
'/webhooks/solidus',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-solidus-signature'] as string
const timestamp = req.headers['x-solidus-timestamp'] as string
const rawBody = req.body.toString()
// 1. Verify the timestamp is recent (within 5 minutes)
const eventTime = parseInt(timestamp, 10) * 1000
const now = Date.now()
if (Math.abs(now - eventTime) > 5 * 60 * 1000) {
res.status(400).json({ error: 'Timestamp too old' })
return
}
// 2. Verify the signature
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex')
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
if (!isValid) {
res.status(401).json({ error: 'Invalid signature' })
return
}
// 3. Parse the event
const event = JSON.parse(rawBody)
// 4. Respond immediately with 200 to acknowledge receipt
res.status(200).json({ received: true })
// 5. Process the event asynchronously
try {
switch (event.type) {
case 'verification.completed':
await handleVerificationCompleted(event.data)
break
case 'verification.failed':
await handleVerificationFailed(event.data)
break
case 'verification.expired':
await handleVerificationExpired(event.data)
break
case 'credential.issued':
await handleCredentialIssued(event.data)
break
case 'credential.revoked':
await handleCredentialRevoked(event.data)
break
default:
console.log('Unknown event type:', event.type)
}
} catch (err) {
console.error('Error processing webhook:', err)
}
}
)
async function handleVerificationCompleted(data: any) {
// Update user record in your database
await db.users.update({
where: { did: data.subjectDid },
data: {
kycStatus: 'verified',
kycLevel: data.level,
credentialId: data.credentialId,
verifiedAt: new Date(),
},
})
}
async function handleVerificationFailed(data: any) {
await db.users.update({
where: { did: data.subjectDid },
data: {
kycStatus: 'failed',
kycFailureReason: data.reason,
},
})
}
async function handleVerificationExpired(data: any) {
await db.users.update({
where: { did: data.subjectDid },
data: { kycStatus: 'expired' },
})
}
async function handleCredentialIssued(data: any) {
await db.credentials.create({
data: {
credentialId: data.credentialId,
subjectDid: data.subjectDid,
type: data.type,
issuer: data.issuer,
issuedAt: new Date(data.issuanceDate),
expiresAt: new Date(data.expirationDate),
txHash: data.txHash,
},
})
}
async function handleCredentialRevoked(data: any) {
await db.credentials.update({
where: { credentialId: data.credentialId },
data: {
revokedAt: new Date(),
revokedBy: data.revokedBy,
revocationReason: data.reason,
},
})
}
app.listen(3001)6. Teslimatı İşleme
Webhook teslimatlarını güvenilir şekilde işlemek için şu uygulamaları izleyin:
Hızlı yanıt verin. Mümkün olduğunca hızlı bir 200 durum kodu döndürün. Yanıt verdikten sonra
olayı eşzamansız olarak işleyin. Uç noktanız çok uzun sürerse (30 saniyeden fazla), teslimat
başarısız olarak işaretlenir.
Yinelenenleri işleyin. Webhook teslimatları yeniden denenebilir, bu yüzden aynı olayı birden
fazla kez alabilirsiniz. Olayları tekilleştirmek için id alanını kullanın.
// Deduplicate using the event ID
const alreadyProcessed = await db.processedEvents.findUnique({
where: { eventId: event.id },
})
if (alreadyProcessed) {
res.status(200).json({ received: true, duplicate: true })
return
}
// Mark as processed before handling
await db.processedEvents.create({
data: { eventId: event.id, processedAt: new Date() },
})HTTPS kullanın. Webhook uç noktaları üretimde HTTPS kullanmalıdır. HTTP uç noktalarına yalnızca
geliştirme sırasında localhost için izin verilir.
7. Yeniden Deneme Politikası
Uç noktanız 2xx olmayan bir durum kodu döndürürse veya 30 saniye içinde yanıt vermezse, Solidus teslimatı üstel geri çekilme (exponential backoff) ile yeniden dener:
| Attempt | Delay after failure |
|---|---|
| 1. yeniden deneme | 1 dakika |
| 2. yeniden deneme | 5 dakika |
| 3. yeniden deneme | 30 dakika |
| 4. yeniden deneme | 2 saat |
| 5. yeniden deneme | 12 saat |
5 başarısız yeniden denemeden sonra, teslimat kalıcı olarak başarısız işaretlenir. Kontrol panelinde veya API aracılığıyla başarısız teslimatları görüntüleyebilirsiniz.
8. Teslimat Günlükleri
Bir webhook uç noktası için teslimat geçmişini inceleyin.
const response = await fetch(
'https://verify.solidus.network/v1/webhooks/wh_3nK8mR2pQ5/deliveries',
{
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
}
)
const deliveries = await response.json()Yanıt:
{
"data": [
{
"id": "del_7rT2nK9mP4",
"eventId": "evt_8nR3kL5mQ2xY",
"eventType": "verification.completed",
"status": "delivered",
"statusCode": 200,
"attemptNumber": 1,
"requestTimestamp": "2026-05-07T12:05:31Z",
"responseTimestamp": "2026-05-07T12:05:31Z",
"duration": 145
},
{
"id": "del_4mL9gK3cE6",
"eventId": "evt_9pQ4rM6sT3wZ",
"eventType": "verification.failed",
"status": "failed",
"statusCode": 500,
"attemptNumber": 1,
"nextRetryAt": "2026-05-07T12:11:30Z",
"requestTimestamp": "2026-05-07T12:05:30Z",
"responseTimestamp": "2026-05-07T12:05:32Z",
"duration": 2045
}
],
"pagination": {
"total": 2,
"page": 1,
"perPage": 25
}
}9. Webhook’ları Test Etme
Doğru yapılandırıldığını doğrulamak için uç noktanıza bir test olayı gönderin.
const response = await fetch(
'https://verify.solidus.network/v1/webhooks/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({
webhookId: 'wh_3nK8mR2pQ5',
eventType: 'verification.completed',
}),
}
)
const result = await response.json()Yanıt:
{
"id": "del_test_9kM2nR5pQ3",
"status": "delivered",
"statusCode": 200,
"duration": 230,
"request": {
"url": "https://myapp.com/webhooks/solidus",
"headers": {
"Content-Type": "application/json",
"X-Solidus-Signature": "a1b2c3d4...",
"X-Solidus-Timestamp": "1746619530"
}
}
}Test olaylarının, gerçek olaylardan ayırt edebilmeniz için evt_test_ önekli bir id’si vardır.
10. Webhook’ları Yönetme
Webhook’ları Listele
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://verify.solidus.network/v1/webhooksBir Webhook’u Güncelle
curl -X PATCH https://verify.solidus.network/v1/webhooks/wh_3nK8mR2pQ5 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["verification.completed", "credential.issued"],
"status": "active"
}'Bir Webhook’u Sil
curl -X DELETE https://verify.solidus.network/v1/webhooks/wh_3nK8mR2pQ5 \
-H "Authorization: Bearer YOUR_API_KEY"Sırrı Döndür (Rotate)
Webhook sırrınız ele geçirilirse, onu döndürün. Eski sır anında geçersiz hale gelir.
curl -X POST \
https://verify.solidus.network/v1/webhooks/wh_3nK8mR2pQ5/rotate-secret \
-H "Authorization: Bearer YOUR_API_KEY"Güvenlik Kontrol Listesi
- Olayları işlemeden önce her zaman
X-Solidus-Signaturebaşlığını doğrulayın - Zaman damgaları 5 dakikadan daha eski olan istekleri reddedin
- Üretimde HTTPS uç noktaları kullanın
- Webhook sırrını güvenli bir şekilde saklayın (ortam değişkeni, sır yöneticisi)
- Olay
idalanını kullanarak olayları tekilleştirin 200döndürdükten sonra olayları eşzamansız olarak işleyin- Kalıcı başarısızlıklar için teslimat günlüklerini izleyin
Sonraki Adımlar
- KYC Entegrasyonu — doğrulama akışını gömün
- Kimlik Bilgisi Akışı — tam kimlik bilgisi yaşam döngüsü
- API Referansı — tam API belgeleri