Graine AI

Send a signed test event

Send one signed test event to an endpoint and report what came back.

Send one signed test event to an endpoint and report what came back.

Give it either a callback_url or the subscription_id of an existing subscription. With subscription_id the test uses that subscription exactly as configured — its URL, method, stored headers and stored signing secret — which is the only way to prove that what is stored actually works.

Not both. A subscription_id together with a DIFFERENT callback_url is a 400. The stored headers and signing secret are write-only and are only ever sent to the subscription's own URL; to test another endpoint, send that callback_url on its own with whatever headers and secret you want used.

This is a test, not a delivery. One attempt, a 10-second budget, no retry ladder, and nothing is written to the delivery ledger. The delivery_id in the response is synthetic and will not appear in GET /v2/webhooks/deliveries.

The payload is shaped like a real event — the same envelope keys, the same signature scheme — with synthetic values and an extra test: true key. The signature is HMAC-SHA256 over "<timestamp>." + body in the X-Graine-Signature header as t=<unix>,v1=<hex>; verify by recomputing over the raw body bytes and rejecting a stale t.

signed comes back false when no secret is configured for the subscription or the organisation. That is the same state the ledger records as signed: false, and it means your signature check can never pass.

Your endpoint's response body is deliberately not echoed — only its status code, an error class and the elapsed time. The URL is re-checked against its RESOLVED addresses before the request is made, because this endpoint dials on demand from our network.

A 202 means the test was attempted; read delivered for the outcome. A non-2xx from your endpoint is a successful test with delivered: false, not an error from this API.

Errors: 400 (neither callback_url nor subscription_id, or a refused URL), 401, 403, 404 (no such subscription in this organisation), 422, 429, 503.

Rate limit: 1000 requests per minute per organization (bucket default). Exceeding it returns 429 with Retry-After; the X-RateLimit-* response headers report your remaining allowance on every call.

POST
/v2/webhooks/test
/v2/webhooks/test

The Authorization access token

Authorization

Authorization
Required
Bearer <token>

Your Graine API key. Create one in the dashboard under Developers, or via POST /v2/api-keys. Send it as Authorization: Bearer <key>.

In: header

Request Body

application/jsonRequired

callback_urlCallback Url | null

Where to send the test. Required unless subscription_id is given.

Maximum length: 2048

subscription_idSubscription Id | null

Test an existing subscription exactly as it is configured — its URL, method, headers and secret.

event_typeEvent Type | null

Event type to stamp on the test payload. Valid event types: all_processing_completed, call_completed, call_corrected, call_ringing, call_started, client_analysis_completed, platform_analysis_completed, recording_completed. Omitting event_types, or sending an empty list, resolves to all_processing_completed — there is no value that means "every event".

agent_idAgent Id | null

Stamped on the test payload only.

method_typeMethod Type | null

POST or PUT. Defaults to POST.

headersHeaders | null

Extra headers for this test only. Nothing is stored.

secretSecret | null

Sign the test with this secret instead of the stored one. Nothing is stored.

Query Parameters

organization_idOrganization Id

Must match the organisation the API key belongs to.

Response Body

202

What we sent and what your endpoint answered.

accepted
Required
Accepted

Always true — the test request was built and attempted.

delivered
Required
Delivered

True when your endpoint answered 2xx.

http_statusHttp Status | null

Status your endpoint returned, or null if the request never completed.

error_classError Class | null

dns_error | timeout | connect_error | tls_error | http_error | null.

error_messageError Message | null

Capped error detail, no response body.

duration_ms
Required
Duration Ms

Wall time of the attempt.

signed
Required
Signed

True when an X-Graine-Signature header was sent. False means no secret is configured for this subscription or organisation.

signature_header
Required
Signature Header

Name of the signature header.

event_type
Required
Event Type

Event type stamped on the test payload.

event_id
Required
Event Id

Unique per test call.

delivery_id
Required
Delivery Id

Synthetic id; it is NOT in the delivery ledger.

callback_hostCallback Host | null

Host we sent the test to.

method_type
Required
Method Type

POST or PUT.

sent_header_names
Required
Sent Header Names

Names of every header we sent. Values are never echoed.

body
Required
Body

The exact test payload we signed and sent.

400

1000 — malformed input the schema could not reject: an unknown event type, a method other than POST/PUT, a reserved header name, or a callback_url pointing at a private, loopback or metadata address.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

401

1100 — missing, unknown or inactive API key. 1101 — a browser session token was presented instead of a gat_ key.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

403

1102 — the key is valid but lacks the scope this endpoint requires, or names another organisation. GET /v2/scopes reports which scopes the key holds.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

404

1200 — no such subscription or delivery in this organisation. Cross-tenant reads answer 404, never 403.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

422

1001 — the body or query failed validation; the message names the first offending field.

error
Required
integer

Stable integer code from the error table. Branch on this, not on the message.

message
Required
string

One human-readable sentence. Wording may change; the code will not.

429

1300 — per-organisation rate limit exceeded. Carries Retry-After.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

503

1501 — the webhook subscription store is unreachable.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

504

1502 — an upstream did not respond in time.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

curl -X POST "https://api.graine.ai/v2/webhooks/test?organization_id=string" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "callback_url": "https://example.com/hooks/graine",
    "subscription_id": "b91d2f77-3ea5-4c58-9d10-6f8b4a2c7e31",
    "event_type": "call_completed",
    "agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
    "method_type": "POST",
    "headers": {
      "X-Tenant": "acme"
    },
    "secret": "whsec_exampleSigningSecretDoNotUse"
  }'

What we sent and what your endpoint answered.

{
  "accepted": true,
  "delivered": true,
  "http_status": 200,
  "error_class": "timeout",
  "error_message": "Read timed out after 10s.",
  "duration_ms": 412,
  "signed": true,
  "signature_header": "X-Graine-Signature",
  "event_type": "all_processing_completed",
  "event_id": "a7f4c1e2-9b3d-5e88-a0c6-3f7d2e9b4c15",
  "delivery_id": "e5b3a1c9-4d72-4f80-b6a3-1c9e7d5f2b48",
  "callback_host": "example.com",
  "method_type": "POST",
  "sent_header_names": [
    "Content-Type",
    "X-Graine-Delivery-Id",
    "X-Graine-Signature"
  ],
  "body": {
    "event_type": "all_processing_completed",
    "event_id": "a7f4c1e2-9b3d-5e88-a0c6-3f7d2e9b4c15",
    "call_id": "dbcffa37-92a1-4ae7-9814-71ca7b8ab3d3",
    "agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
    "test": true
  }
}