Webhooks
Be notified of status updates during workflows such as KYC, Onramp, and Offramp
Webhook Implementation
Alfred signs the webhook events and requests we send to your endpoints. We do so by including a signature in each event’s Signature header. This allows you to validate that the events and requests were sent by Alfred, not by a third party.
Before you can verify Signature signatures for webhook events, you need to retrieve your webhook API key from the Developers page on the Alfred dashboard.
The Signature header contains a timestamp and one signature. The timestamp is prefixed by t=, and the signature is prefixed by s=.
Header: signature: t=1492774577,s=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Alfred generates signatures using a hash-based message authentication code (HMAC) with SHA-256.
Alfred’s webhooks allow you to get automatic updates anytime the status on a KYC, Onramp, or Offramp workflow changes.
eventType
will determine which workflow the status update belongs to.referenceId
will allow Alfred to determine which object to connect the status update to. The reference ID is different depending on the eventType.metadata
will contain additional information to help Alfred process webhook notifications.
Webhook retries
For failed webhooks (all 4xx, 5xx status codes), Alfred requires a minimum of 10 webhook retry attempts with exponential backoff.
Webhook Endpoint
Create Webhook
Use our API Reference to get started creating your webhooks
<https://penny-api-restricted-dev.alfredpay.io/api/v1/third-party-service/penny/webhooks>
Request Body
{
"referenceId": string, (transactionId for onramp,offramp. submissionId for kyc)
"eventType": string, (ONRAMP, OFFRAMP, KYC)
"status": string,
"metadata": json,
}
Example Success Response Body
{
"message": "success"
}
Webhook Secret
-
When sending the webhook information in response to the request, we will include the signature in the header with the following format:
t=1492774577,s=cf2626451f133f50fc207cbc4d029cad0e385d621b4661f08501ec6d0a8473e9
-
Both parties must share the same secret to encrypt and decrypt the signature, allowing validation of the information from AlfredPay.
-
Below is an example of how to decrypt the signature:
import crypto from 'crypto';
async function verifySignature(signatureHeader: string, data: unknown, secret: string): Promise<boolean> { // Extract timestamp and signature from the header const headerParts = signatureHeader.split(','); const timestampPart = headerParts.find(part => part.startsWith('t=')); const signaturePart = headerParts.find(part => part.startsWith('s='));
if (!timestampPart || !signaturePart) { throw new Error('Invalid signature header format.'); }
const timestamp = parseInt(timestampPart.split('=')[1], 10);
const signature = signaturePart.split('=')[1];
// Validate that the timestamp is not too old (5 minutes) const currentTimestamp = Math.floor(new Date().getTime() / 1000); if (Math.abs(currentTimestamp - timestamp) > 300) { throw new Error('Signature timestamp is too old.'); }
// Recreate the signed string
const dataToSign = ${timestamp}.${JSON.stringify(data)}
;
// Generate the signature with the same secret key
const hmac = crypto.createHmac('sha256', secret);
const expectedSignature = hmac.update(dataToSign).digest('hex');
// Compare both signatures
return signature === expectedSignature;
}
KYC event type statuses
status | Description | Metadata |
---|---|---|
COMPLETED | kyc verified on partner side | null |
FAILED | kyc failed on partner side | {"failureReason":string} |
IN_REVIEW | kyc submission is being review by partner | null |
UPDATE_REQUIRED | kyc requires additional info from partner | {"requiredFields":[string]} |
CREATED | kyc submission has been created | null |
Offramp event type statuses
status | Description | Metadata |
---|---|---|
ON_CHAIN_DEPOSIT_RECEIVED | Partner acknowledges on chain receive of funds at their USDT wallet | {"txHash": string} |
TRADE_COMPLETED | Trade of USDT to local fiat currency has been completed | null |
FIAT_TRANSFER_INITIATED | Partner initiated fiat transfer to customer bank account | null |
FIAT_TRANSFER_COMPLETED | Partner fiat transfer to customer bank account settled | null |
FAILED | Offramp failed for a partner specific reason. Partner to provide set of failure reasons | {"failureReason":string} |
Onramp event type statuses
status | Description | Metadata |
---|---|---|
FIAT_DEPOSIT_RECEIVED | Partner has received customer fiat funds. If a quote from original transaction is expired, send a new quoteId in metadata. | null |
TRADE_COMPLETED | Trade of local fiat to USDT has been completed. | null |
ON_CHAIN_INITIATED | Partner initiated on chain send to customer wallet address. | {"txHash":string} |
ON_CHAIN_COMPLETED | On chain send to customer wallet address is complete. | {"txHash": string} |
FAILED | Onramp failed for a partner specific reason. Partner to provide set of failure reasons | {"failureReason": string, "txHash": string(optional)} |
FIAT_TRANSFER_INITIATED | Partner initiated fiat transfer to customer bank account (for deposit refund) | null |
FIAT_TRANSFER_COMPLETED | Partner fiat transfer to customer bank account settled (for deposit refund) | null |
Updated 3 months ago