Webhooks
Unblock API uses webhooks to provide you with real-time, asynchronous updates on the status of various processes. Instead of constantly polling our API for changes, you'll receive push notifications directly to your designated webhook endpoints whenever key events occur. This is crucial for efficient integration and keeping your systems synchronized with Unblock's platform.
⚙️ Setup
Unblock supports a webhook endpoints API that lets you manage multiple webhook endpoints, each with its own URL, optional event filters, and per-endpoint signing secret. This is useful when you want to:
- Send different event types to different URLs (e.g. transactions to one service, KYC to another).
- Use a different secret per endpoint for verification.
- Add or remove endpoints without changing a single global webhook URL.
Managing webhook endpoints
| Action | Endpoint | Description |
|---|---|---|
| Create | POST /merchant/webhook-endpoints | Create a new endpoint with url, description, and optional subscribed_events. Returns uuid, status, url, and signing_secret. |
| List | GET /merchant/webhook-endpoints | List all your endpoints. Optional parameter: status (active, disabled, auto_disabled). |
| Get one | GET /merchant/webhook-endpoints/{id} | Get a single endpoint by UUID. |
| Update | PATCH /merchant/webhook-endpoints/{id} | Update url, status (active/disabled), or subscribed_events. |
| Rotate secret | PATCH /merchant/webhook-endpoints/{id}/secret | Rotate the signing secret for that endpoint. Returns the new signing_secret; the previous one is invalidated. |
| Delete | DELETE /merchant/webhook-endpoints/{id} | Remove the endpoint. It will no longer receive events. |
All of these require merchant API key authentication.
Creating an endpoint
When you create an endpoint you must send:
url(required): HTTPS URL where Unblock will send webhook payloads.description(required): Short description of the endpoint (max 255 characters).subscribed_events(optional): Array of event types to receive. If omitted, the endpoint receives all event types.
Supported subscribed_events values include: fiatToCrypto, cryptoToFiat, KYC, KYB, AML, unblockBankAccount, entityStatusUpdate, walletCreated, linkedBankAccountProfile, linkBankAccount, duplicateUser, senderNameMismatch, otpNotification.
Example request:
POST /merchant/webhook-endpoints
{
"url": "https://api.yourcompany.com/webhooks/transactions",
"description": "Production transaction events",
"subscribed_events": ["fiatToCrypto", "cryptoToFiat"]
}The response includes the endpoint uuid and signing_secret. Store the secret securely and use it to verify webhook authenticity for requests sent to this URL.
Verifying webhooks from endpoints
Each webhook endpoint has its own signing secret. When Unblock sends a webhook to that endpoint’s URL, the request is signed with that endpoint’s secret. See Ensuring authenticity for details on the verification mechanism.
If you rotate the secret via PATCH /merchant/webhook-endpoints/{id}/secret, use the new secret for all subsequent requests to that URL.
Full API reference for webhook endpoints.
🔒 Ensuring Authenticity
Security is paramount. To guarantee that webhooks are genuinely from Unblock and not from a malicious third party, you must verify the authenticity of each webhook you receive. This is done by validating the Authorization header in the webhook request.
Unblock will include the Webhook Secret in the Authorization header of every webhook sent to your URL. Each endpoint has its own signing secret (returned when you create the endpoint or rotate its secret).
Webhook Header Example:
{
"Accept": "application/json",
"Content-type": "application/json",
"Authorization": "API-Key <YOUR_WEBHOOK_SECRET>"
}To verify authenticity:
- Extract the value from the
Authorizationheader of the incoming webhook request. Compare it with the Webhook Secret for that URL. If the values match, the webhook is authentic. If they don't match, discard the webhook as it may be fraudulent.
It is crucial to implement this verification step in your webhook handling logic to maintain the security of your integration.
🗂️ Webhook Delivery and Reliability
Unblock is committed to reliably delivering webhook notifications to your configured endpoint. However, network issues or temporary unavailability on your server's side can sometimes prevent immediate delivery.
-
Retry Attempts: We will attempt to deliver a webhook up to 7 times in total (the initial attempt + 6 retries).
-
Retry Schedule: The delay between retries increases with each attempt, calculated as:
n ** 6 + 2seconds, wherenis the attempt number (starting from n=1 for the first retry).- After 1st failed attempt (n=1): Retry after 16 + 2 = 3 seconds.
- After 2nd failed attempt (n=2): Retry after 26 + 2 = 64 + 2 = 66 seconds (approx. 1 minute).
- After 3rd failed attempt (n=3): Retry after 36 + 2 = 729 + 2 = 731 seconds (approx. 12 minutes).
- After 4th failed attempt (n=4): Retry after 46 + 2 = 4096 + 2 = 4098 seconds (approx. 1 hour 8 minutes).
- After 5th failed attempt (n=5): Retry after 56 + 2 = 15625 + 2 = 15627 seconds (approx. 4 hours 20 minutes).
- After 6th failed attempt (n=6): Retry after 66 + 2 = 46656 + 2 = 46658 seconds (approx. 12 hours 57 minutes).
It is crucial to ensure your webhook endpoint is robust and acknowledges webhooks promptly to avoid unnecessary retries and potential delays in receiving updates. If all 7 attempts fail, the webhook will not be sent again for that specific event.
🗂️ Data and Types
Unblock webhooks follow a consistent structure, making them easy to parse and process. Regardless of the specific event, the webhook body will always adhere to the following JSON format:
{
"type": "webhookType",
"subType": "webhookSubType",
"uuid": "entityUuid",
"data": {
// ... event-specific data ...
}
}-
type: Indicates the broad category of the webhook event. Possible values include:fiatToCryptocryptoToFiatKYCKYBunblockBankAccountlinkedBankAccountProfilelinkBankAccountentityStatusUpdateotpNotification
-
subType: Provides a more granular classification of the event within thetype. For example, underfiatToCrypto, subtypes could beIN_PROGRESS,SUCCESS,FAILED, etc. -
uuid: The universal unique identifier of the user or corporate entity associated with the event. This allows you to easily link the webhook to the relevant user in your system. -
data: A JSON object containing detailed information specific to the webhook event. The structure and fields withindatawill vary depending on thetypeandsubType.
✉️ Webhook Types and Subtypes
Here's a detailed look at each webhook type and their possible subType values:
1. fiatToCrypto Webhooks
fiatToCrypto WebhooksTriggered by events in fiat-to-crypto transactions.
subTypevalues:
subType | Description |
|---|---|
PENDING | Transaction has been created and is awaiting processing. |
IN_PROGRESS | Unblock acknowledges receipt of fiat and is processing the fiat-to-crypto transaction. |
CRYPTO_TRANSFER_ISSUED | Crypto transaction has been sent to the network. data object will include transactionHash and amountCrypto. |
SUCCESS | Crypto transaction is finalized and process completed. |
ON_HOLD | Transaction is temporarily paused. Check data.status for the reason (e.g., ON_HOLD_KYC for KYC requirement, ON_HOLD_PROCESS for AML). Once cleared, the transaction will resume automatically. |
FAILED | Fiat-to-crypto transaction failed. Contact support for assistance. |
REFUNDED | Fiat-to-crypto transaction was refunded to the customer |
dataobject structure forfiatToCrypto webhooks:
NOTE: If you ever need to contact support over a transaction, please provide the transactionUuid value.
{
"status": "string", // Process status (e.g., "IN_PROGRESS", "ON_HOLD_KYC", "SUCCESS")
"transactionUuid": "uuid", // Unique identifier of the fiat-to-crypto transaction
"direction": "fiatToCrypto", // Always "fiatToCrypto"
"amountCrypto": number, // Present on `CRYPTO_TRANSFER_ISSUED` and `SUCCESS` - Amount of crypto transferred
"amountFiat": number, // Always present - Amount of fiat received
"amountRefunded": number, // Present only on subtype REFUNDED. The amount that was refunded, in currencyFiat.
"walletAddress": "string", // Crypto wallet address receiving the crypto
"transactionHash": "string", // Present on `SUCCESS` - Blockchain transaction hash
"currencyFiat": "string", // Always present - Fiat currency (e.g., "EUR")
"currencyCrypto": "string", // Present on `CRYPTO_TRANSFER_ISSUED` and `SUCCESS` - Crypto currency (e.g., "usdc")
"fees": number, // Present on `CRYPTO_TRANSFER_ISSUED` and `SUCCESS` - Unblock's transaction fees in `currencyFiat`
"internalBankAccountId": "uuid", // Unblock bank account UUID where fiat was received
"chain": "string", // Target blockchain for crypto transfer (e.g., "Ethereum")
"senderDetails": { // Returned on fiatToCrypto webhooks
"senderName": "string", // The full name of the account holder who sent the fiat.
"senderCountry": "string", // ISO 3166-1 alpha-2 code of the sender's bank country.
"accountDetails": { // Returned only to certain merchants
// This object contains the sender's bank details in a structured format.
// Example for EUR:
"currency": "EUR",
"iban": "1234567890"
}
} | undefined,
"sendingWalletAddress": "string", // Crypto address, it can be undefined for open-banking
"linkedBankAccountUuid": "string" // Returned when transfer is made with ACH pull
}
2. cryptoToFiat Webhooks
cryptoToFiat WebhooksTriggered by events in crypto-to-fiat transactions (off-ramping). The uuid field will contain the user's unique identifier.
subTypevalues:
subType | Description |
|---|---|
PENDING | Transaction has been created and is awaiting processing. |
IN_PROGRESS | Unblock acknowledges receipt of crypto and is processing the crypto-to-fiat transaction. |
FIAT_TRANSFER_ISSUED | Fiat transfer has been initiated. data object will include amountFiat. |
SUCCESS | Fiat transfer completed successfully. |
ON_HOLD | Transaction is temporarily paused. Check data.status for the reason (e.g., ON_HOLD_KYC, ON_HOLD_PROCESS). Once cleared, the transaction will resume automatically. |
FAILED | Crypto-to-fiat transaction failed. Contact support for assistance. |
LIMIT_BREACHED | User sent crypto amount below the minimum limit for the chain. data object will have details and limit value. No transaction is initiated. |
REFUNDED | Crypto-to-fiat transaction was refunded to the customer |
dataobject structure forcryptoToFiat webhooks:
NOTE: If you ever need to contact support over a transaction, please provide the transactionUuid value
{
"status": "string", // Process status (e.g., "IN_PROGRESS", "ON_HOLD_KYC", "SUCCESS", "PROCESS_INITIATION_FAILED")
"transactionUuid": "uuid", // Unique identifier of the crypto-to-fiat transaction (null for `LIMIT_BREACHED`)
"direction": "cryptoToFiat", // Always "cryptoToFiat"
"amountCrypto": number, // Always present - Amount of crypto received
"amountFiat": number, // Present on `FIAT_TRANSFER_ISSUED` and `SUCCESS` - Amount of fiat transferred
"amountRefunded": number, // Present only on subtype REFUNDED. The amount that was refunded, in currencyCrypto.
"transactionHash": "string", // Always present - Blockchain transaction hash of crypto transfer
"currencyFiat": "string", // Present on `FIAT_TRANSFER_ISSUED` and `SUCCESS` - Fiat currency (e.g., "EUR")
"currencyCrypto": "string", // Always present - Crypto currency (e.g., "usdc")
"fees": number, // Present on `FIAT_TRANSFER_ISSUED` and `SUCCESS` - Unblock's transaction fees in `currencyCrypto`
"sendingAddress": "string", // Blockchain address that sent the crypto
"remoteBankAccountUuid": "uuid", // Unblock UUID of the destination bank account for fiat
"chain": "string", // Blockchain where crypto was received (e.g., "Polygon")
"limit": number, // Present on `LIMIT_BREACHED` - Minimum limit for the chain & token
"beneficiaryDetails": { // Returned on cryptoToFiat webhooks
"receiverName": "string", // The full name of the beneficiary.
"accountDetails": {
// This object contains the destination bank details in a structured format.
// Example for an NGN beneficiary:
"currency": "NGN",
"accountNumber": "1234567890",
"bankCode": "044",
"bankName": "Access Bank"
},
}3. KYC Webhooks
KYC WebhooksTriggered by changes in the Know Your Customer (KYC) verification status for individual users. The uuid field will contain the user's unique identifier.
subTypevalues:
subType | Description |
|---|---|
KYC_NEEDED | User needs to complete KYC to proceed with further transactions. |
PENDING_KYC_DATA | User started KYC but is missing required documents. |
KYC_PENDING | User has submitted documents, KYC verification is in progress. |
SOFT_KYC_FAILED | KYC verification failed, user may have a chance to correct data or provide more info. |
HARD_KYC_FAILED | KYC verification permanently failed, user cannot be served. |
FULL_USER | KYC verification successful, user is fully verified. |
SUSPENDED | User suspended, based on the review performed by our compliance teams. User cannot take any action in the system. |
Refer to the KYC guide for detailed status definitions.
4. KYB Webhooks
KYB WebhooksTriggered by changes in the Know Your Business (KYB) verification status for corporates. The uuid field will contain the corporate's unique identifier.
subTypevalues:
subType | Description |
|---|---|
CREATED | Corporate entity has been successfully created in the Unblock system. |
KYB_PENDING | Corporate KYB verification is in progress. |
ACTIVE | Corporate KYB verification successful, corporate entity is approved and fully active on the platform. |
REJECTED | Corporate KYB verification rejected, as a result of a compliance review. |
5. unblockBankAccount Webhooks
unblockBankAccount WebhooksTriggered by updates to the status of Unblock bank accounts (ledgers). The uuid field will contain the user or corporate unique identifier.
subTypevalues:
subType | Description |
|---|---|
statusUpdate | Status of an Unblock bank account (ledger) has been updated. Check data.status. |
dataobject structure forunblockBankAccount webhooks:
{
"uuid": "string", // Ledger UUID
"currency": "string", // Currency of the ledger (e.g., "EUR", "GBP")
"bank_country": "string", // Bank country code
"iban"?: "string", // Optional: IBAN if applicable
"bic"?: "string", // Optional: BIC/SWIFT code if applicable
"account_number"?: "string", // Optional: Account number if applicable
"sort_code"?: "string", // Optional: Sort code if applicable
"swift_code"?: "string", // Optional: SWIFT code if applicable
"bank_code"?: "string", // Optional: Bank code if applicable
"bank_name"?: "string", // Optional: Bank name if applicable
"status"?: "ACTIVE" | "WAITING_CREATION" | "FAILED_CREATION" | "DISABLED", // Ledger status
"error"?: "string" // Optional: Error message if status is FAILED
}6. linkedBankAccountProfile Webhooks
linkedBankAccountProfile WebhooksTriggered by updates to the status of profile creation for linked bank accounts.
subType values:
subType | Description |
|---|---|
statusUpdate | Status of a profile has been updated. Check data.status. |
data object structure for linkedBankAccountProfile webhooks:
{
"uuid": "string", // Profile UUID
"status"?: "ACTIVE|WAITING_CREATION|FAILED_CREATION|DISABLED", // Profile status
"error"?: "string" // Optional: Error message if status is FAILED_CREATION
}7. linkBankAccount Webhooks
linkBankAccount WebhooksTriggered when the status of a linked bank account (e.g. after the user completes Plaid) changes. Use this to know when an account is ready for ACH pulls or when linking failed, without polling.
Full webhook payload shape:
{
"type": "linkBankAccount",
"subType": "statusUpdate",
"uuid": "<user-uuid-or-company-uuid>",
"data": {
"uuid": "<linked-bank-account-uuid>",
"status": "ACTIVE",
"error": "<optional; present when status is FAILED>"
}
}| Field | Description |
|---|---|
type | Always "linkBankAccount". |
subType | "statusUpdate" — the linked bank account status has changed. |
uuid | User UUID (or company UUID for corporates) — the entity that owns the linked bank account. |
data.uuid | Linked bank account UUID — the account whose status changed. |
data.status | One of: ACTIVE, CHECK_COMPLETED, PENDING, FAILED, DISABLED. |
data.error | Optional. Error message when data.status is FAILED. |
When it is sent:
- After the user completes the bank link flow: when the provider confirms the link →
data.statusbecomesACTIVE(account ready for pulls). - When linking fails (e.g. invalid account type) →
data.statusFAILED, withdata.errorwhen provided. - When the account is disabled or unlinked →
data.statusDISABLED.
8. entityStatusUpdate Webhooks
entityStatusUpdate WebhooksTriggered by updates on the user general status.
subType values:
subType | Description |
|---|---|
BLOCKED | User has been blocked and cannot perform any action on the platform (no onramps or offramps, no user updates). Contact our operations team to get more info on the reason |
UNBLOCKED | User has been unblocked and can now use the platform as usual |
MISSING_TERMS_AND_CONDITIONS_SIGNED | User has completed the KYC flow, but is missing the validation hashes on the terms and conditions. Please refer to Generating Terms and Conditions Hashes for Compliance section to learn more |
data object structure for entityStatusUpdate webhooks:
{
"uuid": "string", // Linked User UUID
"status"?: "BLOCKED|UNBLOCKED|MISSING_TERMS_AND_CONDITIONS_SIGNED", // User status update
"reason"?: "string" // Optional: description of the status update reason
}9. otpNotification Webhooks
otpNotification WebhooksTriggered by events happening on the delivery of the OTP. Currently delivered via email.
subType values:
subType | Description |
|---|---|
EMAIL_DELIVERY_FAILED | The email to deliver the OTP has bounced back |
data object structure for otpNotification webhooks:
{
"failureType": "string", // Currently only BOUNCE is possible
"emailAddress": "string", // The recipient email address that bounced it
"diagnosticCode": "string", // Provided by the email server with a reason on the bounce
"timestamp": "ISO8601", // ISO8601 timestamp on when the bounce occurred
}📝 Summary
Webhooks are essential for building reactive and efficient integrations with the Unblock API. By correctly setting up your webhook URL, verifying authenticity, and parsing webhook payloads, you can receive real-time updates on transaction statuses, KYC/KYB processes, and other critical events, enabling you to build robust and user-friendly payment experiences. Make sure to carefully handle and process each webhook type and subtype to ensure your application logic correctly responds to different events within the Unblock ecosystem.
Updated 20 days ago
