Webhooks And Errors
Verify webhook signatures and handle SDK errors by type.
Two things the SDK handles that are easy to get wrong by hand: proving a webhook came from Kyshi, and telling a retryable failure apart from a permanent one.
Verifying Webhooks
Kyshi signs every webhook with HMAC-SHA256 and sends the signature in X-Kyshi-Signature. The SDK gives you three helpers.
constructEvent
The one to use. It verifies the signature and returns the parsed event, throwing if verification fails:
import express from 'express';
import { Kyshi } from '@kyshi/mor-sdk';
const kyshi = new Kyshi({ secretKey: process.env.KYSHI_SECRET_KEY! });
app.post(
'/webhooks/kyshi',
express.raw({ type: 'application/json' }),
(req, res) => {
try {
const event = kyshi.webhooks.constructEvent(
req.body.toString('utf8'),
req.get('X-Kyshi-Signature')!,
process.env.KYSHI_WEBHOOK_SECRET!,
);
res.sendStatus(200); // Acknowledge first.
void handle(event); // Then do the work.
} catch {
res.sendStatus(401);
}
},
);Pass the raw body. If your framework parses JSON before you see it, re-serialising the object may not reproduce the exact bytes that were signed and verification will fail. express.raw() above is what keeps them identical.
Your endpoint must respond within 10 seconds, so acknowledge before processing.
verifySignature
Returns a boolean instead of throwing, when you want to branch rather than catch:
if (!kyshi.webhooks.verifySignature(rawBody, signature, secret)) {
return res.sendStatus(401);
}generateSignature
Produces a signature for a payload. Useful for testing your own handler without waiting for Kyshi to send something:
const signature = kyshi.webhooks.generateSignature(payload, secret);Both comparisons are constant-time inside the SDK. Do not reimplement this with ===.
Deduplicate
The same business event can arrive more than once. Key on meta.kyshiEventId and make your handler idempotent — see Webhook Events for the full catalogue and payload shape.
Errors
Every failure throws a subclass of KyshiApiError, so you can catch by type rather than inspecting status codes.
| Class | Thrown when | Retry? |
|---|---|---|
KyshiValidationError | 400 or 422 — bad request shape, or a business rule failed | No, fix the request |
KyshiAuthenticationError | 401 or 403 — bad key, or missing permission | No, fix the key |
KyshiTimeoutError | The attempt exceeded timeoutMs | Maybe — see below |
KyshiNetworkError | The request never completed | Maybe — see below |
KyshiApiError | Everything else, including 5xx | Depends on the status |
All of them carry:
| Property | Meaning |
|---|---|
statusCode | HTTP status. 408 for timeouts, 0 for network errors. |
message | Kyshi's message. Log it; do not branch on it. |
code | The API's code field, where present. |
data | The response data, where present. |
import {
KyshiValidationError,
KyshiAuthenticationError,
KyshiTimeoutError,
KyshiNetworkError,
} from '@kyshi/mor-sdk';
try {
await kyshi.transactions.initialize(params);
} catch (error) {
if (error instanceof KyshiValidationError) {
return badRequest(error.message); // Your payload is wrong.
}
if (error instanceof KyshiAuthenticationError) {
return alertOps(error); // Key or permission problem.
}
if (error instanceof KyshiTimeoutError || error instanceof KyshiNetworkError) {
return markUnknown(params.reference); // See below.
}
throw error;
}The Case That Costs Money
KyshiTimeoutError and KyshiNetworkError do not mean the request failed. They mean you did not get an answer. The transaction or payout may well have been created.
Never blindly retry a write after one. Either:
- pass an
idempotencyKeyso the retry is safe, or - look the record up by your own
referencebefore deciding
catch (error) {
if (error instanceof KyshiNetworkError) {
const existing = await kyshi.transactions.verify(params.reference);
if (existing?.status === 'SUCCESS') return existing; // It went through.
return retryOnce(params);
}
}This is also why the SDK will not auto-retry a POST unless you supply an idempotency key. See Configuration.
Next
- Configuration — retry rules and idempotency
- Webhook Events — the event catalogue
- Failure Codes — payment failure categories
Updated 5 days ago
