Project Event Ingestion Guide

This guide explains how an application sends events to Givemora using a project credential. It covers project setup, the event API, batch requests, signed requests, the standard event catalog, payload rules, responses, retries, and troubleshooting.

Base URL: https://givemora.com

1. Set up a project credential

Create or select a project in the Givemora owner workspace. Issue a project credential for each environment the application uses. Supported environments are development, staging, and production; the credential determines the event environment, so do not send an environment property in the event body.

You can issue a credential in the owner dashboard or with the management API. Management tokens and project credentials are different:

To issue a project credential through the management API, use a management token with credentials:manage scope and the project ID:

export GIVEMORA_MANAGEMENT_TOKEN='gm_owner_...'
export GIVEMORA_PROJECT_ID='your-project-id'

curl --fail-with-body --request POST \
  "https://givemora.com/v1/manage/projects/${GIVEMORA_PROJECT_ID}/credentials" \
  --header "Authorization: Bearer ${GIVEMORA_MANAGEMENT_TOKEN}" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: issue-production-ingest-key-001' \
  --data '{"name":"Production event sender","environment":"production"}'

The response contains the project credential in data.token. Save it in a server-side secret manager immediately: the token is shown only when it is issued. Do not put it in browser JavaScript, a mobile app, source control, event payloads, or logs. If it is lost or exposed, revoke it and issue a replacement.

2. Send one event

Send JSON to POST /v1/events. Authenticate with the project credential as a bearer token:

export GIVEMORA_PROJECT_KEY='gm_project_...'

curl --fail-with-body --request POST 'https://givemora.com/v1/events' \
  --header "Authorization: Bearer ${GIVEMORA_PROJECT_KEY}" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: user-usr_123-signed-up-v1' \
  --data '{
    "id": "user-usr_123-signed-up-v1",
    "type": "user.signed_up",
    "occurred_at": "2026-09-24T18:30:00Z",
    "severity": "info",
    "data": {
      "summary": "A new user created an account",
      "user_id": "usr_123",
      "registration_method": "email"
    },
    "actor": {"type": "user", "id": "usr_123"},
    "tags": ["identity", "signup"],
    "correlation_id": "request-abc-123"
  }'

The body above is an example. Use identifiers and values from your application. The credential supplies the project and environment; the body supplies the event type, time, severity, and event data.

Accepted response

A valid event returns HTTP 202 Accepted, for example:

{
  "accepted": true,
  "event_id": "01...",
  "duplicate": false,
  "received_at": "2026-09-24T18:30:01+00:00",
  "routing": "queued",
  "routing_reason": null,
  "routing_explain": null,
  "request_id": "01..."
}

accepted: true means Givemora accepted and stored the event. It does not guarantee that a notification was delivered. Delivery is asynchronous. routing and routing_reason explain what happened to notification routing; see Routing results.

3. Send a batch

For multiple events, send a JSON array to POST /v1/events/batch. Every element is an event object with the same fields as a single event. Include a unique, stable id in every event so each item can be retried safely.

curl --fail-with-body --request POST 'https://givemora.com/v1/events/batch' \
  --header "Authorization: Bearer ${GIVEMORA_PROJECT_KEY}" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '[
    {
      "id":"invoice-inv_800-issued-v1",
      "type":"billing.invoice_issued",
      "severity":"success",
      "data":{
        "workspace_id":"ws_42",
        "invoice_id":"inv_800",
        "invoice_number":"800",
        "amount_minor":1299,
        "currency":"USD",
        "summary":"Invoice 800 was issued"
      }
    },
    {
      "id":"invoice-inv_799-paid-v1",
      "type":"billing.invoice_paid",
      "severity":"success",
      "data":{
        "workspace_id":"ws_42",
        "invoice_id":"inv_799",
        "amount_minor":1299,
        "currency":"USD"
      }
    }
  ]'

The batch response is HTTP 202 and contains an items array. Each result includes its original index; a valid item may be accepted even if another item fails validation or quota checks. Inspect each item rather than treating the batch-level accepted flag as proof that every event succeeded. The batch endpoint uses each event's id for deduplication; do not rely on an Idempotency-Key request header for a batch.

4. Event body reference

Field Requirement and rules
id Optional string, 1–128 characters. Strongly recommended: create a stable unique ID for every logical event and reuse it for retries.
type Required string, 1–100 characters. Must be lowercase canonical segments separated by dots, such as user.signed_up or orders.payment_failed.
occurred_at Optional RFC 3339 timestamp that includes a timezone, such as 2026-09-24T18:30:00Z or 2026-09-24T22:00:00+03:30.
severity Optional info, success, warning, or critical. If omitted, ingestion uses info; it does not automatically apply the catalog's recommended severity.
data Optional JSON object for event-specific fields. data.summary may contain a human-readable summary of up to 500 characters.
data.summary_translations Optional object with up to eight supported-language keys (en, fa, ar, es, fr, de, pt-BR, zh-CN) and a summary string of up to 500 characters for each. Notifications use the matching translation when present; otherwise they use data.summary as supplied.
context Optional JSON object for application, request, deployment, or source context.
actor Optional JSON object describing who or what initiated the action.
subject Optional JSON object describing the entity affected by the action.
tags Optional array of up to 20 distinct strings; each string must be 1–50 characters.
correlation_id Optional string, 1–128 characters, useful for connecting related events across a workflow.

data, context, actor, and subject must be JSON objects, not arrays. The complete body may be nested no deeper than 10 levels. Unknown top-level fields are rejected; put custom properties inside data, context, actor, or subject. Recognized sensitive key names are redacted during validation, but applications must not send secrets in the first place.

The event catalog's required_fields and optional_fields are the recommended contract for each standard event. Ingestion validates the common event envelope, but does not enforce those event-specific fields. Send the recommended required fields so that the event remains useful to your team and notification recipients.

5. Standard event catalog

The public catalog endpoint is GET /v1/event-catalog. It requires no credential and is limited to 60 requests per minute. It returns the versioned catalog, descriptions, categories, recommended severity, suggested fields, and privacy guidance. These names are recommended, not an allowlist: a project may send any type that follows the canonical syntax, unless that project's routing policy filters it.

Event type Meaning Recommended severity Recommended required data Recommended optional data
user.signed_up A user account was created. info user_id registration_method, email_domain
user.logged_in A user authenticated successfully. info user_id method, ip_country
user.logged_out A user signed out. info user_id method
user.email_verified A user verified their email address. success user_id verification_method
authenticator.code_sent A one-time or authenticator code was sent. Never send the code itself. info user_id, channel attempt_id, expires_at
authenticator.code_verified A one-time or authenticator code was verified. success user_id attempt_id, channel
authenticator.code_failed Code verification failed. warning user_id attempt_id, channel, reason
authenticator.mfa_enabled Multi-factor authentication was enabled. success user_id method
authenticator.mfa_disabled Multi-factor authentication was disabled. warning user_id method, reason
security.suspicious_login A suspicious sign-in was detected. warning user_id reason, ip_country, method
security.account_locked An account was locked. warning user_id reason, lock_until, attempt_count
security.password_reset_abuse Suspicious password-reset activity was detected. warning user_id reason, ip_country, attempt_count
security.api_key_created An API key was created. Never include its value. info actor_user_id key_id, scope, workspace_id, project_id
security.api_key_revoked An API key was revoked. Never include its value. warning actor_user_id key_id, reason, workspace_id, project_id
user.deleted A user account was deleted. info user_id deletion_method, reason
user.password_changed A user's password was changed. Never include a password or hash. success user_id method
user.password_reset_requested A password reset was requested. Never include reset tokens or codes. info user_id channel, request_id, expires_at
user.password_reset_completed A password reset was completed. Never include reset tokens or codes. success user_id method, request_id
user.invited A user was invited to an application or workspace. info invited_user_id, inviter_user_id invitation_id, workspace_id, expires_at
user.invitation_accepted A user accepted an invitation. success user_id invitation_id, workspace_id
session.created An authenticated session was created. Never include session tokens or cookies. info user_id session_id, method, ip_country
session.revoked An authenticated session was revoked. Never include session tokens or cookies. info user_id session_id, reason, actor_user_id
access.role_granted A role was granted to a user or service account. info subject_user_id, role actor_user_id, workspace_id, project_id
access.role_revoked A role was revoked from a user or service account. warning subject_user_id, role actor_user_id, workspace_id, project_id
access.permission_denied An authorization check denied an action. warning user_id, permission resource_type, resource_id, reason
project.created A project was created. info project_id workspace_id, slug
project.archived A project was archived. info project_id workspace_id, reason
integration.connected An external integration was connected. Never include credentials. success integration provider, integration_id, workspace_id, project_id
integration.disconnected An external integration was disconnected. warning integration provider, integration_id, workspace_id, project_id, reason
billing.invoice_issued An invoice was issued. success workspace_id, invoice_id, invoice_number, amount_minor, currency plan, due_at
billing.invoice_paid An invoice was paid. success workspace_id, invoice_id, amount_minor, currency invoice_number, payment_id
billing.invoice_overdue An invoice passed its due date unpaid. warning workspace_id, invoice_id, due_at invoice_number, amount_minor, currency
billing.subscription_started A subscription became active. success workspace_id, plan subscription_id, billing_interval
billing.subscription_canceled A subscription was canceled. warning workspace_id subscription_id, plan, reason, canceled_at
billing.payment_succeeded A payment completed successfully. success workspace_id payment_id, currency, amount_minor
billing.payment_failed A payment attempt failed. warning workspace_id payment_id, reason
billing.payment_refunded A payment was refunded. warning workspace_id payment_id, amount_minor, currency, reason
system.job_succeeded A background job completed. success job job_id, duration_ms
system.job_failed A background job failed after application-level handling. critical job job_id, reason, attempt
system.webhook_failed An inbound or outbound webhook failed processing. warning webhook_id provider, reason, attempt

6. Event intake options

Request Use it when Authentication
POST /v1/events Your application can construct Givemora's event JSON. Recommended for direct integrations. Project bearer credential. Givemora HMAC headers are optional.
POST /v1/events/batch Your application has several events ready to send together. Project bearer credential. Givemora HMAC headers are optional.
POST /v1/hooks/{source} A provider posts its native webhook body and you want Givemora to wrap it as an event. Project bearer credential plus a valid signature.

For a generic signed request, send all three headers: X-Givemora-Timestamp (Unix seconds), X-Givemora-Nonce (a unique printable value), and X-Givemora-Signature. Compute the signature over the exact request body bytes:

sha256=hex(HMAC-SHA256(project_credential, timestamp + "." + nonce + "." + raw_body))

The timestamp must be within five minutes of server time by default. A nonce cannot be reused with the same credential. If any Givemora signature header is sent, all three must be valid. Generic webhook ingestion at /v1/hooks/{source} always requires a signature. The ordinary /v1/events and batch endpoints accept bearer authentication without a request signature, unless signature headers are supplied.

For /v1/hooks/{source}, the source is a lowercase identifier of up to 32 characters. Givemora wraps the received JSON body under data and derives an event type from provider headers or the source name. Optional headers can set X-Givemora-Event-Type, X-Givemora-Event-Id, X-Givemora-Occurred-At, and X-Givemora-Severity. Native signature adapters exist for Stripe, GitHub/GitHub App, GitLab, Slack, and Shopify. The verifier uses the project credential as the signature secret; only use a native provider route when that provider can be configured to sign with this same secret. Otherwise, verify the provider signature in your application and forward a normalized event to POST /v1/events.

This Node.js example sends a custom provider-shaped JSON body through the generic signed webhook route. The body is signed exactly as sent; the X-Givemora-Event-* headers provide the canonical event envelope:

import { createHmac, randomUUID } from 'node:crypto';

const credential = process.env.GIVEMORA_PROJECT_KEY;
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = randomUUID();
const body = JSON.stringify({
  user_id: 'usr_123',
  summary: 'A new user created an account',
  registration_method: 'email',
});
const signature = createHmac('sha256', credential)
  .update(`${timestamp}.${nonce}.${body}`)
  .digest('hex');

const response = await fetch('https://givemora.com/v1/hooks/my_app', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${credential}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
    'X-Givemora-Timestamp': timestamp,
    'X-Givemora-Nonce': nonce,
    'X-Givemora-Signature': `sha256=${signature}`,
    'X-Givemora-Event-Type': 'user.signed_up',
    'X-Givemora-Event-Id': 'user-usr_123-signed-up-v1',
    'X-Givemora-Severity': 'info',
  },
  body,
});

console.log(response.status, await response.json());

7. Routing results

Accepted events are stored even when notification routing is paused, muted, or filtered. A successful HTTP response means ingestion succeeded; check the response's routing fields to understand notification handling.

routing Meaning
queued Notification deliveries were queued for active configured destinations and eligible outgoing webhooks. Actual delivery is asynchronous.
paused The project has notifications paused. The event is still stored.
muted A project event policy is currently muted. The event is still stored.
filtered An event-type allowlist, severity threshold, rule, cooldown, or plan restriction suppressed routing. The event is still stored.
heartbeat The event was recorded as a heartbeat signal; it is not a regular notification.
synthetic The event is a synthetic check that is not being sent as a normal notification.

Common routing_reason values include project_paused, muted_until, event_type_not_allowed, severity_below_threshold, rule_not_matched, cooldown_active, paid_channel_restricted, heartbeat_observed, and synthetic_check. A null reason generally means no suppression reason was recorded. The exact explanation, when available, is returned in routing_explain.

To inspect stored events, use a management token with events:read on GET /v1/manage/projects/{project_id}/events. That read endpoint is separate from the project credential; ingestion keys only write events.

8. Idempotency and safe retries

For a single event, send a stable unique id in the body. You may also send an Idempotency-Key header; if both are present, they must match. When the same project, environment, key, and body are sent again, Givemora returns 202 with duplicate: true without storing a second copy. Reusing a key with a different body returns 409 idempotency_conflict.

For batches, set an id on every item. Retry the complete batch after a timeout or transient error; Givemora deduplicates events that were already accepted. This is especially important if a service interruption occurs after some batch items have been processed.

Retry network failures and temporary 5xx errors, plus 429 rate-limit responses, with exponential backoff and jitter. Honor Retry-After when present. Do not repeatedly retry 400, 401, 403, or 422 errors without correcting the request. A 409 idempotency_conflict means the key was already used for a different event body; generate a new event ID only if it is a genuinely different logical event.

9. Limits and errors

The following are the application's default limits; deployments may override the byte and rate settings:

Limit Default
Single event JSON body 256 KiB
Batch JSON body 5 MiB
Events per batch 100
Maximum event nesting depth 10
data.summary length 500 characters
Translated summaries 8 entries, each up to 500 characters
Tags 20; each 1–50 characters
Event type 1–100 characters
Per credential ingestion requests 60 per minute
Per project ingestion requests 120 per minute
Per workspace ingestion requests 300 per minute

The rate limits count HTTP requests; one batch request counts as one request, while each accepted event still counts toward billing and event usage quotas.

HTTP status Typical error codes What to do
400 malformed_json Send valid JSON.
401 unauthenticated, signature_invalid Check the credential, expiry, revocation, and signature.
403 insufficient_scope Use a project credential with events:write; management tokens cannot ingest.
409 idempotency_conflict, signature_replay Use a unique event ID or a new signature nonce.
413 payload_too_large Reduce the event or batch body size.
422 validation_failed, idempotency_key_mismatch, batch_limit_exceeded Correct the body, header, type, timestamp, or batch size.
429 rate_limit_exceeded, billing_quota_exceeded For rate limits, wait for Retry-After; for quota limits, check workspace usage and plan.
503 ingestion_temporarily_unavailable Retry with backoff and the same event IDs.

Error responses include an error.code, a request_id, and, when applicable, details. Log the request ID with your application's own event ID for support and diagnosis, but never log bearer credentials or signature secrets.

10. Security and data handling

Never send passwords, password hashes, one-time codes, reset tokens, session tokens, access or refresh tokens, API key values, provider credentials, secrets, authorization headers, or cookies. This is especially important for authenticator.code_sent: report that the code was sent and include only non-sensitive delivery metadata. Use opaque application identifiers instead of email addresses or other personal data when possible.

Givemora recognizes and redacts sensitive key names such as password, secret, token, access_token, refresh_token, api_key, authorization, and cookie, including nested values. Redaction is a defense in depth, not permission to submit sensitive data.

11. Quick checklist

  1. Create/select the project and issue a project credential for the right environment.
  2. Store the gm_project_... credential in server-side secret storage.
  3. Use a stable unique event id, a canonical event type, and a timezone-aware occurred_at.
  4. Send the event to POST https://givemora.com/v1/events with Authorization: Bearer ... and JSON content type.
  5. Check both the HTTP status and the returned routing / routing_reason.
  6. Retry temporary failures using the same event IDs; inspect each item in a batch response.
  7. Never put credentials, passwords, session tokens, or one-time codes in an event.