Webhooks
Webhooks allow your agent to receive real-time HTTP notifications when events occur on DobProtocol. Instead of polling endpoints for changes, register a webhook URL and DobProtocol will send a POST request to your server whenever a subscribed event fires.
All webhook management endpoints require an API key with the read scope.
Authentication: X-API-Key header (read scope)
Event Types
| Event | Description | Trigger |
|---|---|---|
distribution_created | A new distribution round was created for a pool | Admin creates distribution |
claim_available | A distribution round is now claimable (claim window opened) | Claim delay period ends |
shares_transferred | Shares were transferred between wallets | Direct transfer or airdrop |
pool_created | A new pool was deployed on-chain | Pool initialization |
marketplace_listing_created | New shares listed for sale | Seller creates listing |
marketplace_sale_completed | A marketplace purchase completed | Buyer completes purchase |
crowdfunding_finalized | Crowdfunding target reached, pool activated | Target amount met |
crowdfunding_failed | Crowdfunding deadline passed without reaching target | Deadline expires |
Register Webhook
Register a new webhook endpoint to receive event notifications.
POST /api/agent/webhooks
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL to receive webhook POST requests |
events | string[] | Yes | List of event types to subscribe to |
secret | string | Yes | Shared secret for HMAC signature verification (min 16 characters) |
curl:
curl -X POST https://home.dobprotocol.com/api/agent/webhooks \
-H "X-API-Key: dob_ak_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/dob-events",
"events": [
"distribution_created",
"claim_available",
"marketplace_sale_completed"
],
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0"
}'
Python:
import requests
resp = requests.post(
"https://home.dobprotocol.com/api/agent/webhooks",
headers={"X-API-Key": API_KEY},
json={
"url": "https://your-server.com/dob-events",
"events": [
"distribution_created",
"claim_available",
"marketplace_sale_completed"
],
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0"
}
)
webhook = resp.json()["data"]
print(f"Webhook ID: {webhook['id']}")
Response (201 Created):
{
"success": true,
"data": {
"id": "wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W",
"url": "https://your-server.com/dob-events",
"events": [
"distribution_created",
"claim_available",
"marketplace_sale_completed"
],
"is_active": true,
"created_at": "2026-03-10T12:00:00Z"
}
}
The url must use HTTPS. HTTP URLs are rejected. The secret is never returned in API responses after creation.
Limits: Each API key can register up to 5 webhooks.
List Webhooks
Retrieve all registered webhooks for the API key.
GET /api/agent/webhooks
curl:
curl https://home.dobprotocol.com/api/agent/webhooks \
-H "X-API-Key: dob_ak_your_key_here"
Response (200 OK):
{
"success": true,
"data": [
{
"id": "wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W",
"url": "https://your-server.com/dob-events",
"events": [
"distribution_created",
"claim_available",
"marketplace_sale_completed"
],
"is_active": true,
"failure_count": 0,
"last_triggered_at": "2026-03-10T14:30:00Z",
"created_at": "2026-03-10T12:00:00Z"
}
]
}
Update Webhook
Update the URL, events, or secret for an existing webhook.
PUT /api/agent/webhooks/:id
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Webhook ID |
Request Body (all fields optional):
| Field | Type | Description |
|---|---|---|
url | string | New HTTPS URL |
events | string[] | New event list (replaces existing) |
secret | string | New shared secret (min 16 characters) |
is_active | boolean | Enable or disable the webhook |
curl:
curl -X PUT https://home.dobprotocol.com/api/agent/webhooks/wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W \
-H "X-API-Key: dob_ak_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"events": [
"distribution_created",
"claim_available",
"marketplace_sale_completed",
"pool_created"
]
}'
Response (200 OK):
{
"success": true,
"data": {
"id": "wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W",
"url": "https://your-server.com/dob-events",
"events": [
"distribution_created",
"claim_available",
"marketplace_sale_completed",
"pool_created"
],
"is_active": true,
"updated_at": "2026-03-10T15:00:00Z"
}
}
Delete Webhook
Permanently delete a webhook. No further events will be delivered.
DELETE /api/agent/webhooks/:id
curl:
curl -X DELETE https://home.dobprotocol.com/api/agent/webhooks/wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W \
-H "X-API-Key: dob_ak_your_key_here"
Response (200 OK):
{
"success": true,
"data": {
"id": "wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W",
"deleted": true
}
}
Test Webhook
Send a test event to your webhook URL to verify the integration is working.
POST /api/agent/webhooks/:id/test
curl:
curl -X POST https://home.dobprotocol.com/api/agent/webhooks/wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W/test \
-H "X-API-Key: dob_ak_your_key_here"
Response (200 OK):
{
"success": true,
"data": {
"webhook_id": "wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W",
"test_event_sent": true,
"delivery_status": "delivered",
"response_code": 200,
"response_time_ms": 145
}
}
The test event sent to your URL looks like:
{
"event": "test",
"timestamp": "2026-03-10T15:00:00Z",
"webhook_id": "wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W",
"data": {
"message": "This is a test event from DobProtocol Agent API."
}
}
Webhook Delivery
Request Format
When an event fires, DobProtocol sends a POST request to your webhook URL with the following structure:
Headers:
Content-Type: application/json
X-Dob-Signature: sha256=5d41402abc4b2a76b9719d911017c592...
X-Dob-Event: distribution_created
X-Dob-Delivery: dlv_01HQ3K5M7N8P9Q0R1S2T3U4V5W
X-Dob-Timestamp: 1710000000
Body:
{
"event": "distribution_created",
"timestamp": "2026-03-10T15:00:00Z",
"delivery_id": "dlv_01HQ3K5M7N8P9Q0R1S2T3U4V5W",
"data": {
"pool_address": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
"pool_name": "Solar Farm Alpha",
"network_id": 10,
"round_id": 6,
"token": "USDC",
"total_amount": "3000.00",
"total_shares": 10000,
"claimable_from": "2026-03-10T16:00:00Z"
}
}
Signature Verification
Every webhook delivery includes an X-Dob-Signature header containing an HMAC-SHA256 signature of the request body, computed with the shared secret you provided when creating the webhook.
Always verify the signature before processing webhook events to ensure the request is authentic.
Python verification:
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_a1b2c3d4e5f6g7h8i9j0"
def verify_signature(payload: bytes, signature_header: str) -> bool:
"""Verify the X-Dob-Signature header against the payload."""
if not signature_header.startswith("sha256="):
return False
expected_sig = signature_header[7:] # Remove "sha256=" prefix
computed_sig = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed_sig, expected_sig)
@app.route("/dob-events", methods=["POST"])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get("X-Dob-Signature", "")
if not verify_signature(payload, signature):
abort(401, "Invalid signature")
event = request.json
event_type = event["event"]
data = event["data"]
if event_type == "distribution_created":
print(f"New distribution in {data['pool_name']}: "
f"{data['total_amount']} {data['token']}")
elif event_type == "claim_available":
print(f"Claims now open for round {data['round_id']} "
f"in {data['pool_name']}")
elif event_type == "marketplace_sale_completed":
print(f"Sale completed: {data['shares']} shares "
f"of {data['pool_name']} at {data['price_per_share']}")
return "", 200
if __name__ == "__main__":
app.run(port=8080)
Node.js verification:
curl example shown using Node.js crypto:
# Equivalent in Python with raw HMAC
import hmac, hashlib, json
body = '{"event":"distribution_created","timestamp":"2026-03-10T15:00:00Z",...}'
secret = "whsec_a1b2c3d4e5f6g7h8i9j0"
signature = "sha256=" + hmac.new(
secret.encode(), body.encode(), hashlib.sha256
).hexdigest()
# Compare this with the X-Dob-Signature header
Your Server Must Respond
Your webhook endpoint must return an HTTP 2xx status code within 10 seconds to acknowledge receipt. The response body is ignored.
If your server does not respond with a 2xx status, DobProtocol considers the delivery failed and will retry.
Retry Policy
Failed deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
After 5 consecutive failures, the webhook is automatically deactivated (is_active set to false). You will need to fix the issue and re-enable the webhook via the Update Webhook endpoint:
curl -X PUT https://home.dobprotocol.com/api/agent/webhooks/wh_01HQ3K5M7N8P9Q0R1S2T3U4V5W \
-H "X-API-Key: dob_ak_your_key_here" \
-H "Content-Type: application/json" \
-d '{"is_active": true}'
The failure_count resets to 0 when the webhook is re-enabled.
Event Payload Reference
distribution_created
{
"event": "distribution_created",
"timestamp": "2026-03-10T15:00:00Z",
"data": {
"pool_address": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
"pool_name": "Solar Farm Alpha",
"network_id": 10,
"round_id": 6,
"token": "USDC",
"total_amount": "3000.00",
"total_shares": 10000,
"claimable_from": "2026-03-10T16:00:00Z",
"expires_at": "2027-03-10T15:00:00Z"
}
}
claim_available
{
"event": "claim_available",
"timestamp": "2026-03-10T16:00:00Z",
"data": {
"pool_address": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
"pool_name": "Solar Farm Alpha",
"network_id": 10,
"round_id": 6,
"token": "USDC",
"total_amount": "3000.00",
"amount_per_share": "0.30",
"expires_at": "2027-03-10T15:00:00Z"
}
}
shares_transferred
{
"event": "shares_transferred",
"timestamp": "2026-03-10T14:00:00Z",
"data": {
"pool_address": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
"pool_name": "Solar Farm Alpha",
"network_id": 10,
"from_address": "GBDM6KRXXJHKVYFJPTPW3WBDKUYVCH7NNEI67DDCP7YX4UHX2GODPHGI",
"to_address": "GDJTUK3ER3M7LFHHWQMFANJA3SEC7QP3LNU3NKGFBLWF4QMFR5HR7I4T",
"shares": 500,
"transaction_hash": "a1b2c3d4..."
}
}
pool_created
{
"event": "pool_created",
"timestamp": "2026-03-10T10:00:00Z",
"data": {
"pool_address": "CB7YQNF8TOVZ5V5MXRHXWKQVHU4ZHR5A6OHCOQZSC8M3XZGKK78TYYZ",
"pool_name": "New Energy Pool",
"pool_ticker": "NEP",
"network_id": 10,
"creator_address": "GBDM6KRXXJHKVYFJPTPW3WBDKUYVCH7NNEI67DDCP7YX4UHX2GODPHGI",
"access_type": "p",
"distribution_type": "tr"
}
}
marketplace_listing_created
{
"event": "marketplace_listing_created",
"timestamp": "2026-03-10T11:00:00Z",
"data": {
"sale_address": "SALE_7f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c",
"pool_address": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
"pool_name": "Solar Farm Alpha",
"network_id": 10,
"seller_address": "GDJTUK3ER3M7LFHHWQMFANJA3SEC7QP3LNU3NKGFBLWF4QMFR5HR7I4T",
"shares_amount": 200,
"price_per_share": "1.50",
"payment_token": "USDC"
}
}
marketplace_sale_completed
{
"event": "marketplace_sale_completed",
"timestamp": "2026-03-10T13:00:00Z",
"data": {
"sale_address": "SALE_7f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c",
"pool_address": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
"pool_name": "Solar Farm Alpha",
"network_id": 10,
"seller_address": "GDJTUK3ER3M7LFHHWQMFANJA3SEC7QP3LNU3NKGFBLWF4QMFR5HR7I4T",
"buyer_address": "GDU3QXHDURHXNKFI4H5QC7JTQOCGS63C25FVTTBGROQOLKV7Q2BAWGYP",
"shares": 200,
"price_per_share": "1.50",
"total_price": "300.00",
"payment_token": "USDC",
"transaction_hash": "d4e5f6a7..."
}
}
crowdfunding_finalized
{
"event": "crowdfunding_finalized",
"timestamp": "2026-04-10T00:00:00Z",
"data": {
"pool_address": "CA7QYNF7SOVZ4V4LXQHXWJQVGU3ZGR4A5OHCOQZSC7M2XZFKK67TYXZ",
"pool_name": "Wind Turbine Collective",
"network_id": 10,
"total_raised": "50000.00",
"target_amount": "50000.00",
"contributor_count": 35,
"payment_token": "USDC"
}
}
crowdfunding_failed
{
"event": "crowdfunding_failed",
"timestamp": "2026-04-15T00:00:01Z",
"data": {
"pool_address": "CA7QYNF7SOVZ4V4LXQHXWJQVGU3ZGR4A5OHCOQZSC7M2XZFKK67TYXZ",
"pool_name": "Wind Turbine Collective",
"network_id": 10,
"total_raised": "32500.00",
"target_amount": "50000.00",
"contributor_count": 18,
"refund_status": "processing"
}
}