Payment Callback

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

HeaderDescription
signatureEd25519 signature (v1=...) used to verify authenticity

Payload Structure

FieldTypeDescription
eventstringEvent name (order.updated)
userIdstringUser id

Order

Key fields

FieldTypeDescription
statusstringOrder state (new, partially_paid, completed, expired)
price_amountstringFiat amount
price_currencystringFiat currency (lowercase)
crypto_amountstringFinal crypto amount (set on completion)

Full order object

FieldTypeDescription
iduuidForgingBlock order ID
order_idstringMerchant order reference
crypto_overpaid_amountstringExtra crypto paid
crypto_underpaid_amountstringMissing crypto amount
created_atstringISO timestamp
updated_atstringISO timestamp

Customer

FieldTypeDescription
emailstringCustomer email
commentstringOptional note

Shipping

FieldTypeDescription
first_namestringFirst name
last_namestringLast name
addressstringStreet address
apartmentstringApartment (nullable)
citystringCity
countrystringISO-2 country
postal_codestringZIP / postal code

Watchers

Tracks blockchain payment monitoring jobs.

FieldTypeDescription
jobIdstringWatcher ID
tokenstringToken contract
holderstringReceiving address
networkstringBlockchain network
decimalsnumberToken decimals
cryptoAmountstringExpected crypto
createdAtstringCreated at

Blockchain Transactions

FieldTypeDescription
paidstringAmount paid
tokenstringToken contract
holderstringReceiving wallet
networkstringNetwork
statusstringTransaction status
startingBlocknumberMonitoring starting block
finalizationBlocknumberFinalized block
updatedAtstringLast 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:

  • userId
  • callback_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_amount is finalized once the order reaches completed status or after the 20-minute order lifetime has elapsed
  • One order may emit multiple callbacks as its status evolves.