API reference
Safe Creative Platform API · version 1.0-draft · Try the API in the playground
Generated automatically from the OpenAPI contract of the API. openapi.yaml
Event registration with qualified-trust evidence, operated by Safe Creative as an independent trusted third party.
What the service certifies: that a declaration was received, exactly when, and that it has not been altered since — never the truth of the declared fact.
Base URL: https://platform.safecreative.org/v1.
Modes: every API key is either live (sc_live_…) or test (sc_test_…). Test keys
hit the same endpoints with the same contract, consume no balance, produce no probative
value, and write to a separate per-channel chain. Test mode has daily limits per account (UTC):
10,000 events, 100 MB of declared content and 20 certifications, each of at most 10,000 events;
past a daily limit the API answers 429 test_quota_exceeded with Retry-After until the next day.
Conventions
- Rate limits per API key: 50 req/s sustained for live keys (bursts ×4; an account may have
its own limit), 10 req/s for test keys. Every response carries
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds); a429 rate_limitedaddsRetry-After. - Temporary unavailability: an event is only acknowledged once it is durably stored and replicated. During a transient
storage failure any operation may answer
503 temporarily_unavailablewithRetry-After: retry, with the sameclient_refwhen registering events. - Language: error
messagein English by default, Spanish withAccept-Language: es. - Forced test scenarios: with a test key,
X-SC-Test-Scenario: unpaid | unpaid_limit | rate_limitedreproduces the documented 402/429 responses without touching real state. - Webhook signature: see
WebhookEnvelope.
Formats
- Timestamps in responses and webhooks are UTC, RFC 3339 with the
Zsuffix and exactly six fractional digits (microseconds, the precision they are stored with), e.g.2026-09-28T07:00:16.526317Z. The acknowledgment ofPOST /eventsreturnsreceived_atexactly as it is registered and later certified. - Timestamps in requests (
occurred_at, certification ranges, query filters) accept any RFC 3339 date-time with an offset (Zor±hh:mm) and up to nine fractional digits; they are normalized to UTC and truncated to microseconds. - Hashes are lowercase hexadecimal. Credits and sequence numbers are integers.
Operations
- POST /events — Declare (register) an event
- GET /events — List events of the channel
- GET /events/{event_id} — Get one event with its full proof elements
- GET /events/{event_id}/content — Download the content held for an event
- GET /channel — Get the channel and its sealed configuration versions
- GET /account/balance — Balance and operational status of the channel's owner account
- POST /certifications — Request a certification of a range (asynchronous)
- GET /certifications — List certifications of the channel
- GET /certifications/{certification_id} — Get a certification's status and download links
Webhooks
- certification.completed — A requested certification finished successfully.
- certification.failed — A requested certification failed (includes an incident id).
- balance.low — Balance crossed a user-defined threshold downwards.
- balance.exhausted — First unpaid event registered (balance reached zero).
- events.unpaid_limit_reached — Unpaid-events limit reached; registrations are now being rejected.
- consumption.limit_reached — A user-defined consumption limit was reached.
- recharge.auto_completed — An automatic top-up was charged and its credits added.
- recharge.auto_failed — An automatic top-up failed; automatic top-up has been turned off.
- webhook.test — Test delivery requested from the console.
POST /events Declare (register) an event Try it
Registers one declared event on the channel identified by the API key. The synchronous acknowledgment is the client's receipt: it commits the event's position in the channel sequence.
Idempotency: client_ref must be unique per channel. Retrying with the same
client_ref and identical content returns 200 with the original event; same
client_ref with different content returns 409 and never overwrites.
Exhausted balance: events are still registered, sealed and chained with
status: unpaid, up to the back-office unpaid limit; past it, 402 unpaid_limit_exceeded.
Body
- application/json: NewEventContent | NewEventHashOnly (one of these schemas)
- multipart/form-data: NewEventFile
Responses
| 201 | Event registered (new). | Event |
| 200 | Idempotent replay — same client_ref, identical content; original event returned. | Event |
| 400 | Invalid body or parameters, or modality mismatch (validation_error). | Error |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 402 | Unpaid-events limit reached (unpaid_limit_exceeded). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 409 | client_ref already used on this channel with different content (client_ref_conflict). | Error |
| 413 | Body over the modality limit (payload_too_large). | Error |
| 422 | Blocked by a user-defined consumption limit set to "block" (limit_blocked). | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
| 503 | Temporarily unavailable (temporarily_unavailable): a transient storage failure, e.g. during a database failover. Nothing
was acknowledged: retry after Retry-After with the same client_ref, which idempotency resolves. | Error |
GET /events List events of the channel Try it
Parameters
| from | query | Received-at lower bound (inclusive). RFC 3339 date-time with an offset (Z or ±hh:mm); normalized to UTC and truncated to microseconds (see Formats). |
| to | query | Received-at upper bound (exclusive). RFC 3339 date-time with an offset (Z or ±hh:mm); normalized to UTC and truncated to microseconds (see Formats). |
| seq_from | query | |
| seq_to | query | |
| status | query | |
| limit | query | |
| cursor | query | Opaque pagination cursor. |
Responses
| 200 | Paginated event list, ordered by sequence. | application/json |
| 400 | Invalid body or parameters, or modality mismatch (validation_error). | Error |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
GET /events/{event_id} Get one event with its full proof elements Try it
Parameters
| event_id | path · required |
Responses
| 200 | The event. | Event |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 404 | Resource not found on this channel (not_found). | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
GET /events/{event_id}/content Download the content held for an event Try it
Content modality only (modality: content): returns the declared content exactly as
Safe Creative holds it — the JSON in its canonical form, the one that was hashed, or the
file, with its original name in Content-Disposition. Its digest is the event's hash.
A hash-only event has no content: 404 not_found.
Parameters
| event_id | path · required |
Responses
| 200 | The content — application/json for JSON content, application/octet-stream for a file. | application/json | application/octet-stream |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 404 | Resource not found on this channel (not_found). | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
GET /channel Get the channel and its sealed configuration versions Try it
Responses
| 200 | Channel with current configuration and version history. | Channel |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
GET /account/balance Balance and operational status of the channel's owner account Try it
Lets client systems self-monitor (low balance, unpaid events, operational status).
Responses
| 200 | Balance snapshot. | AccountBalance |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
POST /certifications Request a certification of a range (asynchronous) Try it
Starts the issuance of the signed certification reports (one in Spanish and one in English) plus their evidence archive for a range of this channel, by dates or by sequence numbers.
Rejected with 402 account_not_operational while the owner account has any unpaid
event ("cuenta sin saldo operativo").
Body
- application/json: NewCertification
Responses
| 202 | Accepted; issuance runs asynchronously. | Certification |
| 400 | Invalid body or parameters, or modality mismatch (validation_error). | Error |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 402 | account_not_operational (unpaid events or negative balance) or insufficient_balance (issuance fee, if enabled). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
| 503 | Temporarily unavailable (temporarily_unavailable): a transient storage failure, e.g. during a database failover. Nothing
was acknowledged: retry after Retry-After with the same client_ref, which idempotency resolves. | Error |
GET /certifications List certifications of the channel Try it
Parameters
| status | query | |
| limit | query | |
| cursor | query |
Responses
| 200 | Paginated list, newest first. | application/json |
| 400 | Invalid body or parameters, or modality mismatch (validation_error). | Error |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
GET /certifications/{certification_id} Get a certification's status and download links Try it
Parameters
| certification_id | path · required |
Responses
| 200 | The certification. When completed, includes download URLs (report PDFs and
evidence ZIP; URLs expire after 7 days and can be regenerated by
re-fetching) and the public verification code. | Certification |
| 401 | Missing, invalid or revoked key (invalid_key / revoked_key). | Error |
| 403 | Channel or owner account suspended by Safe Creative (suspended: event registration and certifications are rejected), or owner account disabled (account_disabled: every call is rejected; accounts are disabled after a year without registering events, requesting certifications or buying credits, and support reactivates them). Data and chains are untouched. | Error |
| 404 | Resource not found on this channel (not_found). | Error |
| 429 | Rate limit exceeded (rate_limited) or, with a test key, a test-mode daily limit reached (test_quota_exceeded); includes Retry-After. | Error |
Webhooks
Notices the platform POSTs to the endpoints you configure in the console. They all share the envelope WebhookEnvelope.
POST certification.completed A requested certification finished successfully.
The certification is completed: the reports, the evidence ZIP and the verification code are ready. data.verification_code is
present; fetch GET /certifications/{certification_id} for fresh download URLs. mode is the certification's mode.
data: WebhookCertificationData
POST certification.failed A requested certification failed (includes an incident id).
The certification could not be issued (status: failed): any issuance charge is refunded and data.incident_id identifies
the incident for support. The same range can be requested again. mode is the certification's mode.
data: WebhookCertificationData
POST balance.low Balance crossed a user-defined threshold downwards.
Sent once per downward crossing of each alert threshold set in the console; the threshold re-arms when a purchase or an
adjustment takes the balance back above it. Always live.
data: WebhookBalanceLowData
POST balance.exhausted First unpaid event registered (balance reached zero).
The balance ran out and an event was registered as unpaid (it is still sealed and chained). From now on certifications
are rejected with 402 account_not_operational until a purchase settles the unpaid events. Sent once per exhaustion. Always live.
POST events.unpaid_limit_reached Unpaid-events limit reached; registrations are now being rejected.
The account reached its unpaid-events limit: from now on POST /events answers 402 unpaid_limit_exceeded until a
purchase settles the debt. Always live.
data: WebhookUnpaidLimitData
POST consumption.limit_reached A user-defined consumption limit was reached.
A consumption limit set in the console (per channel or per key, daily or monthly in UTC) reached its maximum. With action
block further registrations answer 422 limit_blocked until the next period; with warn they keep going through. Sent
once per limit and period. Test events do not count. Always live.
POST recharge.auto_completed An automatic top-up was charged and its credits added.
The balance dropped below the automatic top-up threshold set in the console and the chosen pack was bought with the
default payment method at Safe Creative; its credits are already in the balance (after settling any unpaid events).
Safe Creative sends the invoice. Always live.
data: WebhookAutoRechargeData
POST recharge.auto_failed An automatic top-up failed; automatic top-up has been turned off.
The automatic top-up could not be made (data.reason) and has been turned off, keeping its settings: it can be turned
back on in the console. If the purchase was created but not charged, data.payment_url lets the owner pay it manually.
Always live.
data: WebhookAutoRechargeData
POST webhook.test Test delivery requested from the console.
Only sent when the account owner asks for a test delivery of an endpoint from the console: a single attempt, no retries,
and it never counts towards disabling the endpoint. Always live.
data: WebhookTestData
Schemas
NewEventContent
Content modality — JSON token generated by the client's process.
| Field | Type | |
|---|---|---|
| client_ref * | string | Unique event id in the emitting system (idempotency key per channel). |
| payload * | object | The declared content (JSON). Max body size 256 KB. |
| occurred_at | string (date-time) | When the emitter declares the fact happened. Part of the declaration — Safe
Creative proves the reception time, not this value. RFC 3339 date-time with an offset (Z or ±hh:mm); normalized to UTC and truncated to microseconds (see Formats). |
NewEventHashOnly
Hash-only modality — the content never leaves the client's perimeter.
| Field | Type | |
|---|---|---|
| client_ref * | string | |
| hash * | string | Hex-encoded digest of the client-held content. |
| hash_alg * | string: sha-256 · sha-512 | |
| occurred_at | string (date-time) | When the emitter declares the fact happened. RFC 3339 date-time with an offset (Z or ±hh:mm); normalized to UTC and truncated to microseconds (see Formats). |
NewEventFile
Content modality — file attachment. Max 10 MB.
| Field | Type | |
|---|---|---|
| client_ref * | string | |
| file * | string (binary) | |
| occurred_at | string (date-time) | When the emitter declares the fact happened. RFC 3339 date-time with an offset (Z or ±hh:mm); normalized to UTC and truncated to microseconds (see Formats). |
Event
| Field | Type | |
|---|---|---|
| event_id * | string | |
| channel_id * | string | |
| client_ref * | string | |
| sequence * | integer | Strictly increasing, gapless, per channel (per chain in test mode). |
| received_at * | string (date-time) | Service reception time — the time the evidence attests. UTC, microseconds (see Formats). |
| occurred_at | string | null (date-time) | Declared by the emitter; not attested by Safe Creative. Returned in UTC, microseconds (see Formats). |
| hash * | string | Digest of the content (or the client-declared digest in hash-only modality). |
| hash_alg | string | |
| modality * | string: content · hash_only | content = Safe Creative holds the declared JSON or file, downloadable with
GET /events/{event_id}/content; hash_only = only the digest was declared. |
| chain_hash * | string | Chained digest binding this event to the previous one on the channel. |
| status * | string: registered · unpaid · test | unpaid = registered & sealed but pending payment; blocks certifications account-wide. |
| timestamp | EventTimestamp | Qualified-timestamp coverage of this event. |
| config_version * | integer | Channel configuration version in force at reception. |
| mode * | string: live · test |
EventTimestamp
Qualified-timestamp coverage of an event (Event.timestamp).
| Field | Type | |
|---|---|---|
| type * | string: individual · batch · pending · simulated | batch = proven by inclusion in a sealed chain segment of the channel (the sealing policy in use); pending = within the sealing window; simulated = test mode. individual = own qualified timestamp: reserved, not produced today (every live event is proven by its channel's sealed segment); kept so that individual sealing can be offered without breaking the contract. Clients should accept it. |
| reference | string | null | Timestamp or sealed-segment reference once consolidated (seg_… for a sealed segment). |
| sealed_at | string | null (date-time) | When the covering timestamp was obtained. UTC, microseconds (see Formats). |
Channel
| Field | Type | |
|---|---|---|
| channel_id * | string | |
| name * | string | |
| mode_note * | string | Reminder that live and test chains are separate. |
| current_config * | ChannelConfig | |
| config_versions * | ChannelConfig[] |
ChannelConfig
A sealed, immutable configuration version. Changes create new versions; none is rewritten.
| Field | Type | |
|---|---|---|
| version * | integer | |
| sealed_at * | string (date-time) | When this configuration version was sealed (pre-commitment to the facts). UTC, microseconds (see Formats). |
| modality * | string: content · hash_only | |
| name | string | |
| description | string | |
| declarant * | string | Person or entity on whose behalf events are declared and certifications are requested. Presented in reports as a declaration of the channel owner — not verified by Safe Creative. |
| supplementary_info | string | null | Optional client text explaining what the declared data means. |
NewCertification
Range by dates or by sequence numbers (exactly one pair).
| Field | Type | |
|---|---|---|
| from | string (date-time) | Start of the reception range, inclusive. RFC 3339 date-time with an offset (Z or ±hh:mm); normalized to UTC and truncated to microseconds (see Formats). |
| to | string (date-time) | End of the reception range, exclusive; a future value is cut at the moment of the request. RFC 3339 date-time with an offset (Z or ±hh:mm); normalized to UTC and truncated to microseconds (see Formats). |
| seq_from | integer | First sequence number, inclusive. |
| seq_to | integer | Last sequence number, inclusive. |
Certification
| Field | Type | |
|---|---|---|
| certification_id * | string | |
| channel_id * | string | |
| status * | string: processing · completed · failed | |
| progress | number | null | Approximate progress (0–1) while processing. |
| requested_at * | string (date-time) | When the request was accepted. UTC, microseconds (see Formats). |
| completed_at | string | null (date-time) | When the certification was issued (the issue date printed on the reports) or failed. UTC (see Formats). |
| range * | CertificationRange | The range as requested. It is resolved and frozen on acceptance into sequence numbers of the chain. |
| event_count | integer | null | |
| config_versions_in_range | integer[] | |
| report_url | string | null | Download of the signed PDF report in the language of the request (Accept-Language: Spanish or
English, English by default); expiring URL. reports lists both languages. |
| reports | CertificationReport[] | The signed PDF reports: one in Spanish and one in English, signed separately, with the same
verification code and the same evidence archive. Both are equally authentic and each can be
verified on its own. Earlier certifications may have a single bilingual report
(languages: [es, en]). Empty while processing. |
| evidence_url | string | null | Evidence ZIP download (expiring URL). |
| verification_code | string | null | Code for the public verification page. |
| mode * | string: live · test | |
| content_parts | ContentPart[] | Content modality: the declared content, in ZIP parts of up to 1 GB that are
listed with their SHA-256 in the evidence manifest and in the reports (expiring URLs, like
report_url). Empty for hash-only channels and while processing. |
CertificationReport
One signed PDF report of a certification (Certification.reports).
| Field | Type | |
|---|---|---|
| languages * | string[] | Language of the report, [es] or [en]; both in the bilingual report of earlier certifications. |
| url * | string | Download URL (expiring, like report_url). |
| sha256 * | string | SHA-256 of the signed PDF, lowercase hex; the public verification page recognizes it. |
CertificationRange
Range of a certification (Certification.range), as requested: exactly one pair is set, the reception
dates (from, to) or the sequence numbers (seq_from, seq_to); the other pair is null.
| Field | Type | |
|---|---|---|
| from | string | null (date-time) | Start of the reception range, inclusive. UTC, microseconds (see Formats). |
| to | string | null (date-time) | End of the reception range, exclusive; a future to is cut at the moment of the request. UTC, microseconds. |
| seq_from | integer | null | First sequence number, inclusive. |
| seq_to | integer | null | Last sequence number, inclusive. |
ContentPart
One ZIP part of the declared content of a certification (Certification.content_parts).
| Field | Type | |
|---|---|---|
| name * | string | File name, content-part-NNN.zip. |
| url * | string | Download URL (expiring, like report_url). |
| sha256 * | string | SHA-256 of the part, lowercase hex; also listed in the evidence manifest and in the reports. |
| bytes * | integer | Size of the part in bytes. |
AccountBalance
Balance in credits. One live event consumes one credit; credits are bought in packs in the console, priced per region and currency. The balance itself has no currency.
| Field | Type | |
|---|---|---|
| balance * | integer | Available credits. Negative only if a purchase is reverted after its credits were spent. |
| operational * | boolean | false while the account has unpaid events or a negative balance ("cuenta sin saldo operativo"). |
| unpaid_events * | integer | Live events registered without credits and not yet settled (one credit each). |
WebhookEnvelope
Common webhook body, sent as POST with Content-Type: application/json. Headers:
X-SC-Webhook-Id (the envelope id, stable across retries: use it to de-duplicate),
X-SC-Timestamp (Unix seconds) and X-SC-Signature: v1=<hex>, where <hex> is
HMAC-SHA256 with the endpoint secret over <X-SC-Timestamp>.<raw body>. Verify the
signature and reject old timestamps (e.g. more than 5 minutes) before processing. Any 2xx
acknowledges; otherwise retries with exponential backoff (1 min, 5 min, 30 min, 2 h, 6 h,
12 h, 24 h). An endpoint whose deliveries keep failing is disabled and the account is
notified by email. webhook.test is only sent when the account owner asks for a test delivery
from the console: a single attempt, no retries, and it never counts towards disabling the endpoint.
The model of data depends on type. New fields may be added to data over time: ignore the ones you do not know.
type |
data |
Sent when |
|---|---|---|
certification.completed |
WebhookCertificationData |
A certification was issued (verification_code present). |
certification.failed |
WebhookCertificationData |
A certification could not be issued (incident_id present). |
balance.low |
WebhookBalanceLowData |
The balance crossed an alert threshold downwards. |
balance.exhausted |
WebhookBalanceExhaustedData |
The first event was registered as unpaid. |
events.unpaid_limit_reached |
WebhookUnpaidLimitData |
The unpaid-events limit was reached: registrations are rejected. |
consumption.limit_reached |
WebhookConsumptionLimitData |
A consumption limit reached its maximum for the period. |
recharge.auto_completed |
WebhookAutoRechargeData |
An automatic top-up was charged and its credits added. |
recharge.auto_failed |
WebhookAutoRechargeData |
An automatic top-up failed; automatic top-up was turned off. |
webhook.test |
WebhookTestData |
The account owner asked for a test delivery. |
Example (balance.low):
{"id": "ntc_…", "type": "balance.low", "created_at": "2026-09-28T07:00:16.526317Z", "mode": "live",
"data": {"threshold_id": "thr_…", "threshold": 1000, "balance": 999}}
| Field | Type | |
|---|---|---|
| id * | string | Notice id (ntc_…; tst_… for test deliveries), the same on every retry. Also in X-SC-Webhook-Id. |
| type * | string: certification.completed · certification.failed · balance.low · balance.exhausted · events.unpaid_limit_reached · consumption.limit_reached · recharge.auto_completed · recharge.auto_failed · webhook.test | |
| created_at * | string (date-time) | When the notice was generated. UTC, microseconds (see Formats). |
| mode * | string: live · test | Mode of the fact notified. Account and balance notices are always live; certification notices carry the mode of the certification. |
| data * | WebhookCertificationData | WebhookBalanceLowData | WebhookBalanceExhaustedData | WebhookUnpaidLimitData | WebhookConsumptionLimitData | WebhookAutoRechargeData | WebhookTestData | Payload of the notice. Its model depends on type (see the table above). |
WebhookCertificationData
data of certification.completed and certification.failed.
| Field | Type | |
|---|---|---|
| certification_id * | string | Certification id (CERT-2026-0000001, CERT-TEST-2026-0000001 in test). Use it with GET /certifications/{certification_id}. |
| channel_id * | string | |
| status * | string: completed · failed | |
| event_count * | integer | Events in the certified range. |
| verification_code | string | Public verification code (SC-2026-XXXX-XXXX, SC-TEST-… in test). Only in certification.completed. |
| incident_id | string | Incident reference for support. Only in certification.failed. |
WebhookBalanceLowData
data of balance.low.
| Field | Type | |
|---|---|---|
| threshold_id * | string | Id of the alert threshold that was crossed (thr_…). |
| threshold * | integer | The threshold, in credits. |
| balance * | integer | Balance in credits right after the crossing. |
WebhookBalanceExhaustedData
data of balance.exhausted.
| Field | Type | |
|---|---|---|
| channel_id * | string | Channel of the first event registered as unpaid. |
WebhookUnpaidLimitData
data of events.unpaid_limit_reached.
| Field | Type | |
|---|---|---|
| unpaid_limit * | integer | The account's unpaid-events limit. |
| unpaid_events * | integer | Unpaid events at this moment (equal to the limit). |
| unpaid_credits * | integer | Credits owed for them (one per event). |
WebhookConsumptionLimitData
data of consumption.limit_reached.
| Field | Type | |
|---|---|---|
| limit_id * | string | Id of the consumption limit (lim_…). |
| scope * | string: channel · key | Whether the limit applies to a whole channel or to one API key. |
| channel_id * | string | Channel of the event that reached the limit. |
| key_id * | string | null | API key id (key_…) when scope is key; null for channel limits. |
| period * | string: day · month | |
| period_key * | string | The period that reached the limit, UTC calendar day YYYY-MM-DD or month YYYY-MM. |
| max_events * | integer | Events allowed per period. |
| action * | string: warn · block | warn: registrations keep going through. block: 422 limit_blocked until the next period. |
WebhookAutoRechargeData
data of recharge.auto_completed and recharge.auto_failed.
| Field | Type | |
|---|---|---|
| recharge_id * | string | null | The purchase (its Safe Creative sale code). null when it failed before any purchase was created. |
| pack_id * | string | The pack configured for automatic top-up. |
| credits * | integer | null | Credits of the pack bought. null when it failed before any purchase was created. |
| amount * | string | null | Net price of the pack, before taxes, as a decimal string. null when it failed before any purchase was created. |
| currency * | string | null: EUR · GBP · USD · null | Currency of amount, that of the owner's pricing region. |
| threshold * | integer | The automatic top-up threshold, in credits. |
| balance | integer | recharge.auto_completed only: balance in credits after adding the pack. |
| reason | string: payment_failed · sale_creation_failed · payment_method_missing · pack_unavailable · pricing_country_required · daily_limit · account_suspended · custom_pack_inactive | recharge.auto_failed only: payment_failed (the charge was declined), sale_creation_failed (Safe Creative could not
create the purchase, e.g. an expired payment method), payment_method_missing (no valid default payment method),
pack_unavailable (the pack is no longer on sale), pricing_country_required (no billing country), daily_limit
(daily maximum of automatic top-ups reached), account_suspended or custom_pack_inactive (the custom pack it bought
was deactivated; it is turned off at that moment, without any purchase attempt). |
| payment_url | string | null | recharge.auto_failed only: page to pay the purchase manually when it was created but not charged. |
| auto_recharge_disabled | boolean | recharge.auto_failed only: always true; automatic top-up was turned off and keeps its settings. |
WebhookTestData
data of webhook.test.
| Field | Type | |
|---|---|---|
| endpoint_id * | string | The endpoint (whk_…) the test was requested for. |
| message * | string |
Error
| Field | Type | |
|---|---|---|
| code * | string: validation_error · invalid_key · revoked_key · account_not_operational · unpaid_limit_exceeded · insufficient_balance · not_found · client_ref_conflict · payload_too_large · limit_blocked · rate_limited · test_quota_exceeded · internal · temporarily_unavailable · suspended · account_disabled | |
| message * | string | Human-readable; EN by default, ES via Accept-Language. |
| doc_url | string | null | |
| incident_id | string | null | Present on 5xx responses. |