Skip to main content

Webhooks Guide

Webhooks allow you to receive real-time HTTP notifications when events occur in your 123hub account, such as when a payment completes or fails.

How Webhooks Work

1

Event Occurs

A payment status changes (e.g., completed, failed)
2

123hub Sends Notification

We send an HTTP POST request to your configured endpoint with the event payload
3

You Verify the Signature

Your server verifies the X-Data-Hash header to confirm the request is authentic
4

You Process the Event

Your server processes the event and returns a 2xx response
5

Retry if Needed

If delivery fails, we retry with exponential backoff

Setting Up Webhooks

Webhook endpoints are configured through the 123hub merchant dashboard or by your account manager. Provide:
  1. Your HTTPS endpoint URL
  2. The events you want to subscribe to
  3. Your secret key (used for signature verification)
Store your secret key securely. It is used to verify that webhook requests originate from 123hub.

Webhook Events

EventDescription
payment.createdPayment was created
payment.completedPayment completed successfully
payment.failedPayment failed
payment.cancelledPayment was cancelled
payment.refundedPayment was refunded
payout.createdPayout was created
payout.completedPayout completed
payout.failedPayout failed

Webhook Payload

All webhook payloads use the standard multihub envelope format:
{
  "success": true,
  "result": {
    "payment": {
      "amount": {
        "value": 10000,
        "currency": "INR"
      },
      "identifiers": {
        "c_id": 12345,
        "h_id": 1001,
        "p_id": "txn_abc123"
      },
      "status": {
        "status": "success",
        "final": true,
        "success": true
      },
      "timestamps": {
        "created": "2026-01-15T10:30:00Z",
        "updated": "2026-01-15T10:35:00Z",
        "finished": "2026-01-15T10:35:00Z"
      },
      "destination": "in",
      "service_id": 14701
    }
  },
  "request_id": "req_abc123def456",
  "processing_time": 0
}

Payload Fields Reference

Envelope

FieldTypeDescription
successbooleanAlways true for webhook deliveries
resultobjectContains the event data
request_idstringUnique identifier for this webhook delivery
processing_timeintegerServer-side processing time in milliseconds

Payment Object

FieldTypeDescription
amount.valueintegerPayment amount in minor units (e.g., paise, centavos)
amount.currencystringISO 4217 currency code (INR, MXN, etc.)
identifiers.c_idintegerCustomer/merchant identifier
identifiers.h_idintegerHub identifier
identifiers.p_idstringUnique payment/transaction identifier
status.statusstringPayment status (success, fail, pending, cancelled, refunded)
status.finalbooleanWhether this is a terminal (final) status
status.successbooleanWhether the payment was successful
timestamps.createdstringPayment creation time (ISO 8601)
timestamps.updatedstringLast update time (ISO 8601)
timestamps.finishedstring | nullCompletion time (ISO 8601), null if not finished
destinationstringDirection: in for deposits, out for payouts
service_idintegerYour service/application identifier

Webhook Headers

Each webhook request includes these headers:
HeaderDescription
Content-TypeAlways application/json
X-Data-HashSHA-512 signature for verification

Signature Verification

Always verify webhook signatures to ensure requests originate from 123hub.

Verification Algorithm

The signature is a SHA-512 hash of the raw request body concatenated with your secret key:
X-Data-Hash = SHA512(rawRequestBody + secretKey)
You must use the raw request body bytes for verification, not a re-serialized version of the parsed JSON. Re-serializing may change key ordering or whitespace, causing the hash to mismatch.

Implementation Examples

import hashlib
import json

def verify_webhook(body_bytes: bytes, secret_key: str, received_hash: str) -> bool:
    expected = hashlib.sha512(body_bytes + secret_key.encode()).hexdigest()
    return expected == received_hash

# In Flask:
# @app.route('/webhook', methods=['POST'])
# def handle_webhook():
#     body_bytes = request.get_data()  # raw bytes
#     received_hash = request.headers.get('X-Data-Hash')
#
#     if not verify_webhook(body_bytes, SECRET_KEY, received_hash):
#         return jsonify({'error': 'Invalid signature'}), 400
#
#     payload = json.loads(body_bytes)
#     payment = payload['result']['payment']
#     status = payment['status']['status']
#     p_id = payment['identifiers']['p_id']
#
#     if status == 'success' and payment['status']['final']:
#         # Handle completed payment
#         pass
#     elif status == 'fail' and payment['status']['final']:
#         # Handle failed payment
#         pass
#
#     return jsonify({'received': True}), 200

# In FastAPI:
# @app.post('/webhook')
# async def handle_webhook(request: Request):
#     body_bytes = await request.body()
#     received_hash = request.headers.get('X-Data-Hash')
#     is_valid = verify_webhook(body_bytes, SECRET_KEY, received_hash)

Retry Policy

If webhook delivery fails (non-2xx response or timeout), we retry with exponential backoff using the formula min(2^n * 60s, 3600s):
AttemptDelay
1Immediate
22 minutes
34 minutes
48 minutes
516 minutes
After 5 failed attempts, the webhook is marked as failed. Contact your account manager to investigate persistent delivery failures.

Handling Different Event Types

Here is how to determine the event type from the webhook payload:
Deposits have destination: "in". A successful deposit:
{
  "status": { "status": "success", "final": true, "success": true },
  "destination": "in"
}
When you receive a successful deposit, credit the customer’s order and update your records.

In-Request Webhooks

Instead of pre-configuring webhooks, you can pass a webhook_url directly in the payment creation request. This is useful when you want per-payment notification routing or a simpler integration.

How It Works

  1. Include webhook_url in your payment creation request body
  2. 123hub automatically creates (or reuses) a webhook endpoint for your merchant
  3. Events are delivered to this URL in addition to any pre-configured webhooks
  4. The webhook is signed using the same X-Data-Hash mechanism with your secret key

Deduplication

If you send the same webhook_url across multiple payments, the system automatically reuses the existing webhook configuration. No duplicate webhook endpoints are created.

Best Practices

Always Verify Signatures

Never process webhooks without verifying the X-Data-Hash signature first. Use the raw request body bytes for hash computation.

Respond Quickly

Return HTTP 200 immediately, then process events asynchronously. Long-running webhook handlers risk timeouts and unnecessary retries.

Handle Duplicates

Use identifiers.p_id and request_id for idempotency. You may receive the same event more than once due to retries.

Log Everything

Log webhook payloads (including request_id) for debugging and audit trails.
Webhook endpoints must be publicly accessible HTTPS URLs. Self-signed certificates are not supported.
Check status.final before acting. Only process payments where status.final is true. Non-final statuses are intermediate updates and the payment may still change.