OffBlocks
  • πŸ‘‹Welcome to OffBlocks
  • Overview
    • πŸ’‘What we do
    • ✨Core Concepts
    • πŸ“œWhitepaper
    • ⛓️Supported Blockchains and Assets
    • ❔FAQ
  • Developer Guides
    • πŸ—οΈSystem Architecture
    • βš™οΈAPI Integration
      • πŸš€Getting Started
      • πŸ“―Postman
      • πŸ”‘Authentication
      • πŸ”Request Signatures
      • πŸ†”Idempotency
      • ⛓️Blockchain Identifiers
      • πŸ“’Blockchain Updates
      • πŸ“²Sign in With X
      • πŸ’°Fees
      • πŸ•ΉοΈSimulator
      • πŸ”’API Reference
        • πŸ¦Έβ€β™‚οΈCustomers
        • 🏦Accounts
        • πŸ’³Cards
        • πŸ”ƒTransactions
        • πŸͺWebhooks
        • πŸ•ΉοΈSimulator
        • πŸš‚Engine
      • ⏭️States and Transitions
        • πŸ¦Έβ€β™‚οΈCustomer States
        • 🏦Account States
        • πŸ’³Card States
        • πŸ”‘Authorisation States
        • πŸ”ƒTransaction States
    • 🧬Smart Contracts
      • OffBlocksEscrow.sol
      • OffBlocksSmartWalletFactory.sol
      • OffBlocksSmartWallet.sol
      • PendingWithdrawal.sol
      • Interfaces
    • πŸ”’Step-by-step Guide
      • 1️⃣API Credentials
      • 2️⃣Authentication
      • 3️⃣Request Preparation
      • 4️⃣Customer Creation
      • 5️⃣Customer Verification
      • 6️⃣Account Creation
      • 7️⃣Card Issuance
      • 8️⃣Card Authorisations
      • 9️⃣Webhooks
  • Use Cases
    • πŸ’°Wallet Providers
    • πŸ§‘β€πŸŒΎDeFi Apps
    • 🏦Neo-banks and FinTechs
Powered by GitBook
On this page
  1. Developer Guides
  2. API Integration
  3. API Reference

Webhooks

API reference for webhook-related endpoints

PreviousTransactionsNextSimulator

Last updated 1 year ago

Subscription

Managing Webhook Subscriptions

You can create a new webhook subscription or update an existing one using a PUT request to /webhooks. Whether you prefer to receive notifications for all entities through a single callback URL or require more granular configuration, you can achieve this by using the types parameter in the request body.

Retrieving Webhook Subscriptions

You can retrieve and existing webhook subscription using a GET request to /webhooks/{webhookId}.

To retrieve all active webhook subscriptions, make a GET request to /webhooks.

Removing Webhook Subscriptions

If you wish to remove a webhook subscription, you can do so by sending a DELETE request to /webhooks/{webhookId}.

Schema

This portion of our API is in active development and may be updated frequently. Please, reach out to us if something is not working or not working as expected

Every webhook event object has a discriminator webhookType field followed by all fields included in a respective entity schema:

Customer Updated Event
{
  "webhookType": "customer.updated",
  "eventId": "db63696c-10b2-42c1-8a07-ac9a15985744",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "externalId": "string",
  "chainId": "account-chain-id",
  "status": "initiating",
  "createdAt": "2024-01-11T18:34:38.367Z",
  "updatedAt": "2024-01-11T18:34:38.367Z"
}
Account Updated Event
{
  "webhookType": "account.updated",
  "eventId": "d403095e-df22-4946-9932-63b04ced15b4",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "chainId": "account-chain-id",
  "status": "initiating",
  "type": "card_account",
  "currencies": [
    "string"
  ],
  "createdAt": "2024-01-11T18:34:38.367Z",
  "updatedAt": "2024-01-11T18:34:38.367Z"
}
Card Updated Event
{
  "webhookType": "card.updated",
  "eventId": "7fe835d9-7ab5-4db0-a5fe-e37a86735dc5",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "virtual",
  "name": "string",
  "network": "visa",
  "maskedPan": "string",
  "expirationDate": "mm/yyyy",
  "billingAddress": {
    "firstLine": "string",
    "secondLine": "string",
    "city": "string",
    "state": "string",
    "country": "string",
    "postCode": "string"
  },
  "status": "issuing",
  "createdAt": "2024-01-11T18:34:38.367Z",
  "updatedAt": "2024-01-11T18:34:38.367Z"
}
Authorisation Updated Event
{
  "webhookType": "authorisation.updated",
  "eventId": "64727de0-1245-4a12-a8a7-bbe8383d9cfd",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "cardId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "pending",
  "amount": "decimal",
  "currency": "string",
  "merchantAmount": "decimal",
  "merchantCurrency": "string",
  "exchangeRate": "decimal",
  "merchant": {
    "id": "string",
    "name": "string",
    "mcc": 0,
    "country": "string",
    "city": "string"
  },
  "createdAt": "2024-01-11T18:34:38.368Z",
  "updatedAt": "2024-01-11T18:34:38.368Z"
}
Transaction Updated Event
{
  "webhookType": "transaction.updated",
  "eventId": "64727de0-1245-4a12-a8a7-bbe8383d9cfd",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "cardId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "authorisationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "processing",
  "direction": "debit",
  "amount": "decimal",
  "currency": "string",
  "merchantAmount": "decimal",
  "merchantCurrency": "string",
  "exchangeRate": "decimal",
  "merchant": {
    "id": "string",
    "name": "string",
    "mcc": 0,
    "country": "string",
    "city": "string"
  },
  "tokenAmount": "decimal",
  "token": "asset-chain-id",
  "fees": [
    {
      "type": "partner",
      "tokenAmount": "decimal"
    }
  ],
  "chainTransactions": [
    {
      "chainId": "transaction-chain-id",
      "createdAt": "2024-01-11T18:34:38.368Z"
    }
  ],
  "createdAt": "2024-01-11T18:34:38.368Z",
  "updatedAt": "2024-01-11T18:34:38.368Z"
}

Webhook retry policy

We consider a webhook successfully delivered when we receive a success status code (2xx) from your webhook URI.

We apply this retry policy for all supported entities.

Handling duplicated webhooks

OffBlocks can't guarantee that you only receive a single webhook notification for each update. As such, your integration should have logic that can handle receiving multiple webhooks for a given update. Similarly, due to networking limitations, we can't guarantee webhooks to be delivered in a correct chronological order. Your system needs to be able to handle out-of-order notifications.

For example, imagine OffBlocks sends a transaction.updated webhook with status processed, but doesn't receive a 200 response due to network issues from the recipient. In this case, OffBlocks sends an extra transaction.updated webhook with status processed as it can't confirm the previous one was received, regardless of the current status of the transaction. Your integration should be able to handle such possibilities.

Webhook Notification Verification

Every webhook notification we send contains an HTTP signature that you can verify to ensure it has been sent from us, a verified source. Although it is not required, we strongly recommend verifying notification signature, or you risk accepting fraudulent events.

Each webhook notification contains a set of headers for you to verify, following the signing procedure outlined in Request Signatures. There are a few steps required to verifying the webhook signatures. We'll go through each one below.

Retrieve Webhook Verification Key

Extract Webhook Headers

On each webhook HTTP request, there are three headers that will be needed for verification.

  • Content-Digest: a sha-512 hash of serialised request content, or body.

Verification

Now that you have the webhook body, webhook signature headers, and the public key, you can use these data points to do the verification.

For example, if you're using Go-based backend, you can use following code snippet to verify webhook request's digest and signature:

reader := req.Body

body, err := io.ReadAll(reader)
if err != nil {
	w.WriteHeader(http.StatusBadRequest)
	return
}

block, _ := pem.Decode([]byte(key))

pki, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
	w.WriteHeader(http.StatusBadRequest)
	return
}

pkpub := pki.(*ecdsa.PublicKey)
verifier := httpsig.NewVerifier(
	httpsig.WithVerifyEcdsaP384Sha384(keyId, pkpub),
)

digestor := httpsig.NewDigestor(httpsig.WithDigestAlgorithms(httpsig.DigestAlgorithmSha512))

err = digestor.Verify(body, req.Header)
if err != nil {
	w.WriteHeader(http.StatusBadRequest)
	return
}

err = verifier.Verify(httpsig.MessageFromRequest(req))
if err != nil {
	w.WriteHeader(http.StatusBadRequest)
	return
}

If we receive any other status code (for instance, if your API is temporarily unavailable), we will start retrying. Our retry policy is similar to . We will immediately perform some fast retries and then start waiting increasingly longer. We will keep retrying for up to 24 hours. If we continue to receive any other status codes than 2xx after retrying for 24 hours, we will discard the webhook.

To verify the signature, you need to grab a public key (encoded in ). This is available through our REST API and can be retrieved by doing a GET call on the /webhooks/verification-key endpoint.

Signature-Input: a Dictionary structured field containing the metadata for a signatures generated from components within the HTTP request (see for more details)

Signature: a Dictionary structured field containing a message signatures generated from the signature context of the target message (see for more details)

All webhook requests are signed using as a signing algorithm, while digest is always calculated using sha-512 hashing algorithm.

We actively develop a to support HTTP message signatures as well as contribute to a . Reach out to us if you're using other technologies on your backend and we'll be happy to assist with any issues integrating HTTP message signatures.

βš™οΈ
πŸ”’
πŸͺ
jittered exponential backoff
PEM format
IETF specification
IETF specification
ECDSA with curve P-384 DSS and SHA-384
Go library
Node.js library

Retrieve webhook subscription information

get

Retrieves webhook subscription information

Authorizations
Path parameters
webhookIdstring Β· uuidRequired

Unique webhook ID

Header parameters
SignaturestringRequired

HTTP message signature

Signature-InputstringRequired

HTTP message signature input

Responses
200
Successful operation
application/json
400
Invalid request
401
Not authorised
404
Webhook not found
500
Internal error
get
GET /v1/webhooks/{webhookId} HTTP/1.1
Host: api.offblocks.xyz
Authorization: Bearer JWT
Signature: text
Signature-Input: text
Accept: */*
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "callbackUrl": "https://example.com/callback",
  "types": [
    "customer.updated"
  ],
  "createdAt": "2025-05-09T07:09:47.310Z",
  "updatedAt": "2025-05-09T07:09:47.310Z"
}

Retrieve all active webhooks

get

Retrieves all active webhooks

Authorizations
Header parameters
SignaturestringRequired

HTTP message signature

Signature-InputstringRequired

HTTP message signature input

Responses
200
Successful operation
application/json
400
Invalid request
401
Not authorised
500
Internal error
get
GET /v1/webhooks HTTP/1.1
Host: api.offblocks.xyz
Authorization: Bearer JWT
Signature: text
Signature-Input: text
Accept: */*
[
  {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "callbackUrl": "https://example.com/callback",
    "types": [
      "customer.updated"
    ],
    "createdAt": "2025-05-09T07:09:47.310Z",
    "updatedAt": "2025-05-09T07:09:47.310Z"
  }
]

Remove webhook subscription

delete

Removes webhook subscription

Authorizations
Path parameters
webhookIdstring Β· uuidRequired

Unique webhook ID

Header parameters
SignaturestringRequired

HTTP message signature

Signature-InputstringRequired

HTTP message signature input

Idempotency-Keystring Β· uuidRequired

Idempotency key (UUID)

Responses
204
Successful operation
400
Invalid request
401
Not authorised
404
Webhook not found
500
Internal error
delete
DELETE /v1/webhooks/{webhookId} HTTP/1.1
Host: api.offblocks.xyz
Authorization: Bearer JWT
Signature: text
Signature-Input: text
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000
Accept: */*

No content

Retrieve webhook verification key

get

Retrieves webhook verification key

Authorizations
Header parameters
SignaturestringRequired

HTTP message signature

Signature-InputstringRequired

HTTP message signature input

Responses
200
Successful operation
application/json
400
Invalid request
401
Not authorised
500
Internal error
get
GET /v1/webhooks/verification-key HTTP/1.1
Host: api.offblocks.xyz
Authorization: Bearer JWT
Signature: text
Signature-Input: text
Accept: */*
{
  "keyId": "123e4567-e89b-12d3-a456-426614174000",
  "key": "Ynl0ZXM="
}
  • Subscription
  • Managing Webhook Subscriptions
  • PUTCreate new or update an existing webhook
  • Retrieving Webhook Subscriptions
  • GETRetrieve webhook subscription information
  • GETRetrieve all active webhooks
  • Removing Webhook Subscriptions
  • DELETERemove webhook subscription
  • Schema
  • Webhook retry policy
  • Handling duplicated webhooks
  • Webhook Notification Verification
  • Retrieve Webhook Verification Key
  • GETRetrieve webhook verification key
  • Extract Webhook Headers
  • Verification

Create new or update an existing webhook

put

Creates new webhook subscription or updates an existing one

Authorizations
Header parameters
SignaturestringRequired

HTTP message signature

Signature-InputstringRequired

HTTP message signature input

Idempotency-Keystring Β· uuidRequired

Idempotency key (UUID)

Content-DigeststringRequired

Content digest

Body
idstring Β· uuidOptional

Unique ID of the webhook to be updated

callbackUrlstring Β· uriRequired

URL to be subscribed for receiving events

Example: https://example.com/callback
Responses
200
Successful operation
application/json
400
Invalid request
401
Not authorised
500
Internal error
put
PUT /v1/webhooks HTTP/1.1
Host: api.offblocks.xyz
Authorization: Bearer JWT
Signature: text
Signature-Input: text
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000
Content-Digest: text
Content-Type: application/json
Accept: */*
Content-Length: 119

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "callbackUrl": "https://example.com/callback",
  "types": [
    "customer.updated"
  ]
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "callbackUrl": "https://example.com/callback",
  "types": [
    "customer.updated"
  ],
  "createdAt": "2025-05-09T07:09:47.310Z",
  "updatedAt": "2025-05-09T07:09:47.310Z"
}