Send a webhook test
POST /webhooks/test - Send a signed synthetic webhook event to verify handler setup.
0Gate is the primary public integration path for hosted payment, ramp, and swap experiences. Keep secret-key operations on your server and hand only browser-safe values to the widget.
Endpoint
| Field | Value |
|---|---|
| Method | POST |
| Path | /v1/webhooks/test |
| Area | Webhooks |
| Operation id | sendTestWebhook |
| Auth boundary | Secret key from your server. |
Use it for
Enqueue a synthetic webhook.test event to your configured webhook_url so you can verify signature handling and endpoint reachability end-to-end without waiting for a real event. The call returns the queued delivery; poll GET /webhooks/deliveries for its outcome.
The test event is signed and carries exactly the same headers as a real event, so a handler that accepts the test is wired correctly for production traffic. The payload's data.livemode is always false, and the platform never emits a webhook.test event on its own.
Set your webhook_url first
If your account has no webhook_url configured yet, this call returns 400. Configure the endpoint, then send a test.
Request
No request body and no path or query parameters. Authenticate with your sk_* secret key. This route is not idempotent: every successful call enqueues a brand-new webhook.test event with its own event_id, so calling it twice delivers two test events. Send one test, then poll GET /webhooks/deliveries for its outcome rather than re-firing.
Server-side, secret key only
This endpoint requires an sk_* secret key. pk_* publishable keys are browser-safe and only bootstrap the embed; they cannot send a test event.
Response
Returns the queued WebhookDelivery with event_type: webhook.test and status: pending. For the full field list, see the WebhookDelivery record.
| Field | Type | Use it for |
|---|---|---|
object | string | Always webhook_delivery. |
id | string | Delivery id; poll its outcome on the deliveries list. |
event_id | string | Stable event id, mirrored in X-0bit-Event-Id; your dedupe key. |
event_type | string | Always webhook.test. |
status | enum | pending until the worker attempts delivery. |
Signature and headers
The test POST to your webhook_url carries the same signature and companion headers as every real event:
| Header | Value |
|---|---|
Gate-Signature | t=<unix-ts>,v1=<hex> — the timestamped HMAC (recipe below). |
X-0bit-Timestamp | <unix-ts> — the same value as t= in Gate-Signature. |
X-0bit-Event-Id | <uuid> — the event id; your dedupe key. |
X-0bit-Event-Type | webhook.test. |
Verify the signature
The v1 value is an HMAC-SHA256 (hex) over the string formed by the timestamp, a literal ., then the raw request body — sign the exact bytes you received. Reject anything outside the 300-second skew tolerance, and compare with a constant-time equality check.
const crypto = require('crypto');
function verify(rawBody, header, secret, skewSeconds = 300) {
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=')),
);
const t = parseInt(parts.t, 10);
if (Math.abs(Date.now() / 1000 - t) > skewSeconds) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(parts.v1, 'hex'),
);
}The same verification applies to every real event in the event catalogue.
Examples
curl -X POST https://gate-api.0bit.app/v1/webhooks/test \
-H "Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"object": "webhook_delivery",
"id": "whd_test_67a1f3b9e4b0c10001230001",
"event_id": "c0ffee00-1111-2222-3333-444455556666",
"event_type": "webhook.test",
"target_url": "https://partner.example/webhooks/0bit",
"status": "pending",
"attempts": 0,
"last_response_status": null,
"last_error": null,
"next_attempt_at": "2026-01-01T00:00:01Z",
"delivered_at": null,
"created_at": "2026-01-01T00:00:01Z",
"updated_at": "2026-01-01T00:00:01Z"
}This is the body POSTed to your webhook_url:
{
"id": "c0ffee00-1111-2222-3333-444455556666",
"type": "webhook.test",
"created_at": 1716729600,
"data": {
"livemode": false,
"message": "This is a test event from 0Bit."
}
}Delivery and retry
The test event follows the same delivery rules as real events: at-least-once delivery, a 10-second timeout per attempt, retries at 1m, 5m, 30m, 2h, then dead-letter after 5 attempts. See delivery and retry for the full semantics.
Errors
All errors use the unified envelope and carry an X-Request-Id response header. Branch on code/type/statusCode, not on the free-form message.
{
"type": "invalid_request",
"code": "invalid_request",
"message": "Example error using fake data.",
"request_id": "req_test_000000000123",
"doc_url": null,
"statusCode": 400
}| Status | type | When it happens |
|---|---|---|
400 | invalid_request | No webhook_url is configured for your account yet. Set one, then send a test. |
401 | unauthorized | Missing or invalid secret key. |
429 | rate_limited | Throttled. This route allows up to 10 requests per minute. Back off and retry. |
5xx | server_error | Transient server failure. Retry with bounded backoff. |
Production rules
- Keep secret keys on your server. This endpoint requires an
sk_*key. - Configure
webhook_urlbefore testing; an unset endpoint returns400. - Verify
Gate-Signatureover the raw body, enforce the 300-second skew, and compare in constant time — the test is the safe place to prove this works. - This route is not idempotent: each call enqueues a new test event. Send one and poll its outcome rather than re-firing on a slow response.
- Branch on machine-readable status, error code, object id, and request id.
- Treat examples and placeholder ids as fake data only.
Public boundary
This reference covers partner-scoped endpoint behavior, authentication, idempotency, webhook verification, and support-safe records. Internal operations, administrative routes, settlement venues, and unsupported availability claims are outside the public API contract.