Skip to content

Webhooks API Reference ​

All webhook endpoints are mounted at /v1/webhooks.

Endpoints summary ​

MethodPathDescription
GET/v1/webhooksGet current webhook configuration
PUT/v1/webhooksCreate or update webhook configuration
POST/v1/webhooks/testSend a test webhook to the configured URL
GET/v1/webhooks/secretReveal the current signing secret
GET/v1/webhooks/deliveriesList delivery jobs (paginated)
GET/v1/webhooks/deliveries/{job_id}Get delivery job detail with all attempt logs
POST/v1/webhooks/deliveries/{job_id}/retryRe-queue a failed delivery for immediate retry

GET /v1/webhooks ​

Returns the current webhook configuration for the authenticated user.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "url": "https://yourserver.com/tcf-webhook",
    "is_active": true,
    "has_secret": true,
    "events": ["payment.completed", "payment.expired", "withdrawal.completed"],
    "updated_at": "2026-05-15T09:00:00Z"
  }
}

has_secret is a boolean — the secret value is never returned via the API.

PUT /v1/webhooks ​

Create or update the webhook configuration. This is an upsert — if no config exists it is created; if one exists it is updated.

Request body:

FieldTypeRequiredConstraints
urlstringYesMust start with https://
secretstringNoIf omitted, a secret is auto-generated. Provide to set your own.
is_activebooleanNoDefault: true
eventsarray of stringsNoEvent type strings or ["*"] for all. Default: ["*"]

Example request:

json
{
  "url": "https://yourserver.com/tcf-webhook",
  "secret": "my-strong-secret-at-least-32-chars",
  "is_active": true,
  "events": ["payment.completed", "payment.expired_selection", "withdrawal.completed"]
}

Response (200 OK):

json
{
  "success": true,
  "data": {
    "url": "https://yourserver.com/tcf-webhook",
    "is_active": true,
    "has_secret": true,
    "events": ["payment.completed", "payment.expired_selection", "withdrawal.completed"],
    "updated_at": "2026-05-16T12:00:00Z"
  }
}

Passing an empty string as secret clears the signature requirement (deliveries are then sent unsigned — not recommended). Omitting secret preserves the existing secret, or auto-generates one on first setup.

Error codes:

CodeCondition
VALIDATION_ERRORURL is not HTTPS / too long, events is empty, or the list contains an unknown event type

GET /v1/webhooks/secret ​

Returns the raw webhook signing secret so you can configure HMAC verification on your server. The secret is stored encrypted and can be revealed at any time.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "secret": "whsec_..."
  }
}

You can also reveal the secret in the Client Portal → Dashboard → Webhooks → Reveal secret.

POST /v1/webhooks/test ​

Send a synthetic test webhook delivery to your configured URL. Useful for verifying your endpoint and signature verification code.

The test payload is shaped like a payment.completed event with synthetic (non-real) data.

Request body: none required

Response (200 OK):

json
{
  "success": true,
  "data": {
    "delivered": true,
    "status_code": 200,
    "response_body": "OK",
    "duration_ms": 145,
    "payload_sent": {
      "event": "payment.completed",
      "timestamp": "2026-05-16T12:00:00.123456+00:00",
      "data": { "payment_id": "test-00000000-0000-0000-0000-000000000000", "status": "COMPLETED", ... }
    }
  }
}

The test delivery is signed with your configured secret (if any) and carries the same X-TCF-Event / X-TCF-Delivery / X-TCF-Signature headers as real deliveries. No delivery job is recorded in your history.

Error codes:

CodeCondition
NOT_FOUNDNo webhook configuration exists yet
VALIDATION_ERRORWebhook URL is not configured or inactive

GET /v1/webhooks/deliveries ​

List webhook delivery jobs for the authenticated user, newest first.

Query parameters:

ParameterTypeDescription
statusstringFilter: pending, processing, failed, success, permanently_failed
eventstringFilter by event type (e.g. payment.completed)
pageintegerDefault: 1
per_pageintegerDefault: 20, max: 100

Response (200 OK):

json
{
  "success": true,
  "data": {
    "total": 42,
    "page": 1,
    "page_size": 20,
    "total_pages": 3,
    "jobs": [
      {
        "job_id": "d1e2f3a4-...",
        "event": "payment.completed",
        "status": "success",
        "attempt_count": 1,
        "source_type": "payment",
        "source_id": "9f1b2c3d-...",
        "created_at": "2026-05-16T12:05:00Z",
        "last_attempt_at": "2026-05-16T12:05:02Z",
        "last_response_status": 200,
        "next_attempt_at": null
      }
    ]
  }
}

GET /v1/webhooks/deliveries/ ​

Get full detail for a delivery job, including all attempt logs.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "job_id": "d1e2f3a4-...",
    "event": "payment.completed",
    "status": "success",
    "attempt_count": 1,
    "max_attempts": 10,
    "source_type": "payment",
    "source_id": "9f1b2c3d-...",
    "payload": { "event": "payment.completed", ... },
    "created_at": "2026-05-16T12:05:00Z",
    "last_attempt_at": "2026-05-16T12:05:02Z",
    "last_response_status": 200,
    "next_attempt_at": null,
    "logs": [
      {
        "id": "log-uuid",
        "attempted_at": "2026-05-16T12:05:02Z",
        "response_status": 200,
        "response_body": "OK",
        "error_message": null,
        "success": true,
        "duration_ms": 145
      }
    ]
  }
}

POST /v1/webhooks/deliveries/{job_id}/retry ​

Manually re-queue a failed or permanently_failed delivery job for immediate retry. The attempt counter is reset so up to max_attempts fresh attempts are made. Jobs already success or currently processing are rejected.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "job_id": "d1e2f3a4-...",
    "status": "pending"
  }
}

Error codes:

CodeCondition
NOT_FOUNDJob does not exist
FORBIDDENJob belongs to another user
CONFLICTJob already succeeded or is currently processing

TokenCashFlow Documentation