Biklo for developersTESTsandbox-api.biklo.comchange

Register an endpoint

POST/v1/webhooks

scope webhooks.manage

The signing secret is returned ONCE. Verify every delivery: X-Biklo-Signature is t=<timestamp>,v1=hmac_sha256(timestamp + "." + raw_body, secret) — the same scheme Stripe uses. An empty events list means every event.

Request body#

FieldTypeRequiredDescription
urlstringrequiredWhere we POST the events. Must parse as an absolute URL (invalid_url, 400) and must be HTTPS in production (https_required, 400) — event payloads carry order ids and status. Registering the same URL twice on one integration returns endpoint_exists (409).

Not supported Plain http:// is refused in production. The SANDBOX allows it, so you can point a localhost receiver at it while writing your handler.

eventsarrayoptionalWhich events to send. An empty or missing array means EVERY event, including ones added after you register — that is the recommended setting, and the only one that keeps working as the API grows. Listing names explicitly freezes the subscription to those names. An unrecognised name is rejected with unknown_event (400), which names the offenders. GET /v1/webhooks returns available_events for the authoritative list.

Accepts order.created · order.status_changed · order.cancelled · order.items_changed · return.requested · return.delivered · return.escalated · return.resolved · message.received · question.asked · product.approved · product.rejected · payout.updated · notification.sent

headersarray|objectoptionalExtra request headers to send with every delivery to this endpoint. Either shape works: a list of {name, value} as shown, or a plain object {"X-Api-Key": "value"}. For a receiver behind an API gateway that checks a key of its own before routing the request. At most 5, names must be valid HTTP tokens, values at most 500 characters; anything else is invalid_header (400). Stored encrypted and never returned — GET /v1/webhooks shows the names and the first four characters only. This is NOT authentication: a fixed header value says nothing about the body it arrived with, so verify X-Biklo-Signature regardless.

Not supported Content-Type, User-Agent, transport headers and anything starting X-Biklo- are rejected — they are set by us, and overriding them would break the signature you are meant to verify.

headers[].namestringoptionalThe header name, e.g. X-Api-Key. A valid HTTP token: letters, digits and -.
headers[].valuestringoptionalWhat to send. Stored encrypted, never returned, and never written to a delivery record.

Example

{
    "url": "https://erp.example.com/hooks/biklo",
    "events": [
        "order.created",
        "order.status_changed"
    ],
    "headers": [
        {
            "name": "X-Api-Key",
            "value": "the value your gateway expects"
        }
    ]
}

Response#

FieldTypeDescription
idintegerThe new endpoint id.
urlstringWhere we will POST events.
eventsstring[]What it is subscribed to.
is_activebooleantrue on creation.
headersarrayThe custom headers you registered, as {name, preview}. The values are never returned, not even here, so keep your own record of them.
headers[].namestringThe header name we send.
headers[].previewstringThe first four characters of the value, then dots. Enough to tell two credentials apart, useless to anyone who does not already have it.
secretstringThe signing secret, shown HERE AND NOWHERE ELSE. Store it now — it cannot be retrieved later, only replaced by deleting the endpoint and creating another. Every delivery is signed with it, and verifying that signature is what makes a webhook trustworthy.
secret_notestringA reminder that the secret appears once.

Example

{
    "id": 1,
    "url": "https://erp.example.com/hooks/biklo",
    "events": [
        "order.created",
        "order.status_changed"
    ],
    "is_active": true,
    "headers": [
        {
            "name": "X-Api-Key",
            "preview": "the ••••••••"
        }
    ],
    "secret": "whsec_…",
    "secret_note": "Shown once."
}

Failures use the shared error envelope — a machine-readable code and a request_id to quote to support. Error handling.

curl -X POST "https://sandbox-api.biklo.com/v1/webhooks" \
  -H "Authorization: Bearer bk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://erp.example.com/hooks/biklo",
    "events": [
        "order.created",
        "order.status_changed"
    ],
    "headers": [
        {
            "name": "X-Api-Key",
            "value": "the value your gateway expects"
        }
    ]
}'
<?php

$ch = curl_init('https://sandbox-api.biklo.com/v1/webhooks');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'POST',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer bk_test_YOUR_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
      'url' => 'https://erp.example.com/hooks/biklo',
      'events' => [
        0 => 'order.created',
        1 => 'order.status_changed',
      ],
      'headers' => [
        0 => [
          'name' => 'X-Api-Key',
          'value' => 'the value your gateway expects',
        ],
      ],
    ]),
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);
using System.Net.Http;
using System.Net.Http.Headers;

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "bk_test_YOUR_KEY");

var request = new HttpRequestMessage(
    new HttpMethod("POST"),
    "https://sandbox-api.biklo.com/v1/webhooks");

request.Content = new StringContent(
    @"{ ""url"": ""https://erp.example.com/hooks/biklo"", ""events"": [ ""order.created"", ""order.status_changed"" ], ""headers"": [ { ""name"": ""X-Api-Key"", ""value"": ""the value your gateway expects"" } ] }",
    System.Text.Encoding.UTF8,
    "application/json");

var response = await client.SendAsync(request);
var json = await response.Content.ReadAsStringAsync();
import requests

response = requests.post(
    "https://sandbox-api.biklo.com/v1/webhooks",
    headers={"Authorization": "Bearer bk_test_YOUR_KEY"},
    json={
        "url": "https://erp.example.com/hooks/biklo",
        "events": [
            "order.created",
            "order.status_changed"
        ],
        "headers": [
            {
                "name": "X-Api-Key",
                "value": "the value your gateway expects"
            }
        ]
    },
)

data = response.json()
const response = await fetch("https://sandbox-api.biklo.com/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: "Bearer bk_test_YOUR_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "url": "https://erp.example.com/hooks/biklo",
      "events": [
          "order.created",
          "order.status_changed"
      ],
      "headers": [
          {
              "name": "X-Api-Key",
              "value": "the value your gateway expects"
          }
      ]
  })
});

const data = await response.json();
import java.net.http.*;
import java.net.URI;

var client = HttpClient.newHttpClient();

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://sandbox-api.biklo.com/v1/webhooks"))
    .header("Authorization", "Bearer bk_test_YOUR_KEY")
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString(
        "{ \"url\": \"https://erp.example.com/hooks/biklo\", \"events\": [ \"order.created\", \"order.status_changed\" ], \"headers\": [ { \"name\": \"X-Api-Key\", \"value\": \"the value your gateway expects\" } ] }"))
    .build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());