Skip to content

Webhooks

Webhooks allow you to receive real-time notifications about events. When an event occurs (e.g., an invoice status changes), iBuy sends an HTTP POST request to your configured URL with event details.

How It Works

  1. You configure a webhook URL in your shop settings or via the hook_url field when creating an invoice.
  2. When a relevant event occurs, iBuy sends a POST request to your URL.
  3. Your server must respond with HTTP 200 or 204 to acknowledge receipt.
  4. If delivery fails, iBuy will retry up to 5 times.

WARNING

Events may be delivered out of order. If you need to process them sequentially, use the event_created_at field to sort them.

Authentication

Every webhook request includes an Authorization header with your shop's API token hash:

Authorization: Bearer <token_hash>

You should verify this token hash matches your expected value to ensure the request is genuinely from iBuy.

The token hash is a SHA-256 hex digest of your API key. To verify the webhook, compute the hash of your API key and compare it with the Authorization header value.

javascript
const crypto = require("crypto");

function hashToken(token) {
  return crypto.createHash("sha256").update(token).digest("hex");
}
python
import hashlib

def hash_token(token: str) -> str:
    return hashlib.sha256(token.encode()).hexdigest()
go
import (
    "crypto/sha256"
    "encoding/hex"
)

func HashToken(token string) string {
    sum := sha256.Sum256([]byte(token))
    return hex.EncodeToString(sum[:])
}

Request Format

All webhook requests are sent as POST with Content-Type: application/json. The body has the following structure:

json
{
  "shop_id": "uuid-of-your-shop",
  "type": "event_type",
  "body": { },
  "event_created_at": 1700000000000
}
FieldTypeDescription
shop_idstring (UUID)Your identifier
typestringThe event type (see Event Types below)
bodyobjectEvent-specific payload
event_created_atnumberUnix timestamp in milliseconds when the event was created

Event Types

invoice_status_change

DANGER

For ASSIGNED status we are returning invoice_requisites_change event instead because requisites are changed as well.

Sent when the status of an invoice changes (e.g., from PENDING to PAID_BY_USER).

Body:

json
{
  "id": "invoice-uuid",
  "external_id": "inv-ext-001",
  "requisite_id": "requisite-uuid",
  "status": "PAID_BY_USER",
  "amount": "1499.00",
  "created_at": "2025-12-01T12:00:00Z",
  "expires_at": "2025-12-01T12:30:00Z",
  "updated_at": "2025-12-01T12:05:00Z",
  "metadata": {
    "user_id": "user_42",
    "cart_id": "cart_777"
  }
}
FieldTypeDescription
idstring (UUID)Invoice identifier
external_idstring or nullYour external reference (if provided during creation)
requisite_idstring (UUID) or nullPayment requisite identifier (may be null if not yet assigned)
statusstringNew invoice status (see Invoice Statuses)
amountstringInvoice amount as a decimal string
created_atstringInvoice creation timestamp (ISO 8601)
expires_atstringInvoice expiration timestamp (ISO 8601)
updated_atstringLast update timestamp (ISO 8601)
metadataobject or nullCustom metadata you provided when creating the invoice

invoice_requisites_change

Sent when a payment method (requisite) is assigned or changed for an invoice.

Body:

json
{
  "id": "invoice-uuid",
  "external_id": "inv-ext-001",
  "requisite_id": "requisite-uuid",
  "status": "ASSIGNED",
  "amount": "1499.00",
  "created_at": "2025-12-01T12:00:00Z",
  "expires_at": "2025-12-01T12:30:00Z",
  "updated_at": "2025-12-01T12:03:00Z",
  "metadata": {
    "user_id": "user_42",
    "cart_id": "cart_777"
  }
}
FieldTypeDescription
idstring (UUID)Invoice identifier
external_idstring or nullYour external reference (if provided during creation)
requisite_idstring (UUID)Payment requisite identifier (always present for this event)
statusstringCurrent invoice status (see Invoice Statuses)
amountstringInvoice amount as a decimal string
created_atstringInvoice creation timestamp (ISO 8601)
expires_atstringInvoice expiration timestamp (ISO 8601)
updated_atstringLast update timestamp (ISO 8601)
metadataobject or nullCustom metadata you provided when creating the invoice

Invoice Statuses

See Invoice Statuses for the full list of statuses and their flow.

Responding to Webhooks

Your endpoint must return one of the following HTTP status codes to acknowledge the webhook:

  • 200 OK
  • 204 No Content

Any other status code is treated as a delivery failure and will trigger a retry.

WARNING

Your webhook endpoint must respond within 2 seconds. If the request times out, it will be treated as a failure and retried.

Retry Policy

If webhook delivery fails (timeout, non-200/204 response, or network error), iBuy will retry the delivery up to 5 times with increasing delays between attempts.

TIP

Make sure your webhook handler is idempotent — the same event may be delivered more than once in case of retries.

Example: Handling a Webhook

javascript
const express = require("express");
const app = express();

app.use(express.json());

app.post("/webhooks/ibuy", (req, res) => {
  const { shop_id, type, body, event_created_at } = req.body;

  // Verify the authorization token
  const token = req.headers["authorization"]?.replace("Bearer ", "");
  if (token !== process.env.IBUY_WEBHOOK_TOKEN) {
    return res.status(401).send();
  }

  switch (type) {
    case "invoice_status_change":
      console.log(`Invoice ${body.id} status changed to ${body.status}`);
      // Handle status change...
      break;
    case "invoice_requisites_change":
      console.log(`Invoice ${body.id} requisites updated`);
      // Handle requisites change...
      break;
  }

  res.status(204).send();
});

app.listen(3000);
python
from flask import Flask, request

app = Flask(__name__)

@app.route("/webhooks/ibuy", methods=["POST"])
def handle_webhook():
    token = request.headers.get("Authorization", "").replace("Bearer ", "")
    if token != EXPECTED_TOKEN:
        return "", 401

    data = request.json
    event_type = data["type"]
    body = data["body"]

    if event_type == "invoice_status_change":
        print(f"Invoice {body['id']} status changed to {body['status']}")
        # Handle status change...
    elif event_type == "invoice_requisites_change":
        print(f"Invoice {body['id']} requisites updated")
        # Handle requisites change...

    return "", 204