ForgingBlock sends a payment callback to the merchant’s callback_url whenever an order status changes.
You could configure callback_url in Dashboard under Account Settings → Integrations → Callback URL
Headers
| Header | Description |
|---|---|
signature | Ed25519 signature (v1=...) used to verify authenticity |
Payload Structure
| Field | Type | Description |
|---|---|---|
event | string | Event name (order.updated) |
userId | string | User id |
Order
Key fields
| Field | Type | Description |
|---|---|---|
status | string | Order state (new, partially_paid, completed, expired) |
price_amount | string | Fiat amount |
price_currency | string | Fiat currency (lowercase) |
crypto_amount | string | Final crypto amount (set on completion) |
Full order object
| Field | Type | Description |
|---|---|---|
id | uuid | ForgingBlock order ID |
order_id | string | Merchant order reference |
crypto_overpaid_amount | string | Extra crypto paid |
crypto_underpaid_amount | string | Missing crypto amount |
created_at | string | ISO timestamp |
updated_at | string | ISO timestamp |
Customer
| Field | Type | Description |
|---|---|---|
email | string | Customer email |
comment | string | Optional note |
Shipping
| Field | Type | Description |
|---|---|---|
first_name | string | First name |
last_name | string | Last name |
address | string | Street address |
apartment | string | Apartment (nullable) |
city | string | City |
country | string | ISO-2 country |
postal_code | string | ZIP / postal code |
Watchers
Tracks blockchain payment monitoring jobs.
| Field | Type | Description |
|---|---|---|
jobId | string | Watcher ID |
token | string | Token contract |
holder | string | Receiving address |
network | string | Blockchain network |
decimals | number | Token decimals |
cryptoAmount | string | Expected crypto |
createdAt | string | Created at |
Blockchain Transactions
| Field | Type | Description |
|---|---|---|
paid | string | Amount paid |
token | string | Token contract |
holder | string | Receiving wallet |
network | string | Network |
status | string | Transaction status |
startingBlock | number | Monitoring starting block |
finalizationBlock | number | Finalized block |
updatedAt | string | Last update |
Signature Verification
All callbacks are signed using Ed25519 (NaCl).
It is recommended to verify the signature before processing the payload.
Public Key
NACL_PUBLIC_KEY=BiLQpQIzfU+De/xeSq/rIZAaql9nUFwUcNH7SvFKkf8=(Base64-encoded)
What Is Signed
A canonical message derived from:
userIdcallback_url
This binds the callback to the correct merchant and endpoint.
Verification Example
'use strict'
const nacl = require('tweetnacl')
function canonicalWebhookMessage ({ userId, callbackUrl }) {
return Buffer.from(
`user:$USERID\ncallback:${callbackUrl}`,
'utf8'
)
}
const publicKey = Buffer.from(
process.env.NACL_PUBLIC_KEY,
'base64'
)
// From HTTP header: "v1=..."
const signature = Buffer.from(
req.headers.signature.replace(/^v1=/, ''),
'base64url'
)
const message = canonicalWebhookMessage({
userId: MERCHANT_USER_ID,
callbackUrl: MERCHANT_CALLBACK_URL
})
const isValid = nacl.sign.detached.verify(
message,
signature,
publicKey
)
if (!isValid) {
throw new Error('Invalid webhook signature')
}<?php
function canonicalWebhookMessage(string $userId, string $callbackUrl): string {
return "user:$userId\ncallback:$callbackUrl";
}
$publicKey = sodium_base642bin(
getenv('NACL_PUBLIC_KEY'),
SODIUM_BASE64_VARIANT_ORIGINAL
);
// Header: signature: v1=...
$signature = sodium_base642bin(
preg_replace('/^v1=/', '', $_SERVER['HTTP_SIGNATURE']),
SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING
);
$message = canonicalWebhookMessage(
MERCHANT_USER_ID,
MERCHANT_CALLBACK_URL
);
if (!sodium_crypto_sign_verify_detached($signature, $message, $publicKey)) {
throw new Exception('Invalid webhook signature');
}
from nacl.signing import VerifyKey
import base64
def canonical_webhook_message(user_id, callback_url):
return f"user:USER_ID\ncallback:{callback_url}".encode("utf-8")
public_key = base64.b64decode(os.environ["NACL_PUBLIC_KEY"])
signature = base64.urlsafe_b64decode(
request.headers["signature"].replace("v1=", "")
)
message = canonical_webhook_message(
MERCHANT_USER_ID,
MERCHANT_CALLBACK_URL
)
VerifyKey(public_key).verify(message, signature)
package main
import (
"crypto/ed25519"
"encoding/base64"
"fmt"
)
func canonicalWebhookMessage(userId, callbackUrl string) []byte {
return []byte(fmt.Sprintf("user:%s\ncallback:%s", userId, callbackUrl))
}
publicKey, _ := base64.StdEncoding.DecodeString(os.Getenv("NACL_PUBLIC_KEY"))
signature, _ := base64.RawURLEncoding.DecodeString(
strings.TrimPrefix(r.Header.Get("Signature"), "v1="),
)
message := canonicalWebhookMessage(
MERCHANT_USER_ID,
MERCHANT_CALLBACK_URL,
)
if !ed25519.Verify(publicKey, message, signature) {
panic("Invalid webhook signature")
}
Notes
- Callbacks may be retried — handlers must be idempotent.
- It is recommended to verify the signature before trusting order data.
crypto_amountis finalized once the order reachescompletedstatus or after the 20-minute order lifetime has elapsed- One order may emit multiple callbacks as its status evolves.