Custom Events#

Create custom event types to track application-specific data alongside messaging events.

Overview#

While Outeract automatically creates events for messages, you can create custom events for:

  • Order updates
  • Payment confirmations
  • Support ticket status
  • User actions
  • System events

Naming Rules#

Custom events can use any valid name - no special prefix required.

Event type names must:

  • Use lowercase letters, numbers, dots, and underscores only
  • Match the pattern: ^[a-z0-9_.]+$
  • Not use reserved prefixes: message.*, user.*, system.*, link_code.*

Examples#

order.created
payment.completed
support.ticket.opened
subscription.upgraded
purchase
analytics.page_view
**Convention:** Many apps use a prefix like `order.*` or `support.*` to namespace events by domain. This is recommended but not required.

Creating Custom Events#

Events are created in a user’s event stream, so userId is required. Additional graph edges are optional:

mutation CreateCustomEvent {
  createEvent(
    userId: "123e4567-e89b-12d3-a456-426614174000"
    eventType: "order.created"
    payload: {
      order_id: "order_12345"
      customer_id: "cust_xyz"
      total: 99.99
      currency: "USD"
      items: [
        { sku: "WIDGET-001", quantity: 2, price: 49.99 }
      ]
    }
    edges: [
      {
        edgeType: "placed_by"
        targetNodeType: "identity"
        targetNodeId: "identity-uuid"
      }
    ]
  ) {
    id
    eventTypeName
    payload
    createdAt
    edges {
      edgeType
      targetNodeType
    }
  }
}

Event Schemas#

Define schemas to validate custom event payloads.

Create Schema#

Event schemas are managed in the console under Settings → Event Schemas. A schema has a name and an optional JSON Schema document that inbound payloads are validated against:

Name: order.created

JSON Schema:

{
  "type": "object",
  "required": ["order_id", "total"],
  "properties": {
    "order_id": {
      "type": "string",
      "pattern": "^order_[a-zA-Z0-9]+$"
    },
    "total": {
      "type": "number",
      "minimum": 0
    },
    "currency": {
      "type": "string",
      "enum": ["USD", "EUR", "GBP"],
      "default": "USD"
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "sku": { "type": "string" },
          "quantity": { "type": "integer", "minimum": 1 },
          "price": { "type": "number" }
        }
      }
    }
  }
}

Enable Enforce validation on the schema to reject non-conforming events.

Schema Validation#

With enforced validation, events that don’t match the schema are rejected:

{
  "errors": [
    {
      "message": "Validation failed: 'total' is required",
      "extensions": {
        "code": "VALIDATION_ERROR",
        "path": ["payload", "total"]
      }
    }
  ]
}

Querying Custom Events#

By Event Type#

query {
  events(
    eventTypes: ["order.created"]
    first: 20
  ) {
    edges {
      node {
        id
        payload
        createdAt
      }
    }
  }
}

By Payload Field#

Payload filters match on field equality:

query {
  events(
    eventTypes: ["order.created"]
    payloadFilters: [{ field: "order_id", value: "order_12345" }]
  ) {
    edges {
      node {
        id
        payload
      }
    }
  }
}

Filter to events that have an edge targeting a specific node:

query {
  events(
    eventTypes: ["order.created"]
    relatedNodeId: "identity-uuid"
    relatedNodeType: "identity"
    relatedEdgeType: "placed_by"
  ) {
    edges {
      node {
        id
        payload
        edges {
          edgeType
          targetNodeType
          targetNodeId
        }
      }
    }
  }
}

Linking Events#

mutation {
  createEvent(
    userId: "user-uuid"
    eventType: "support.ticket.opened"
    payload: {
      ticket_id: "ticket_123"
      subject: "Help with order"
      priority: "high"
    }
    edges: [
      {
        edgeType: "opened_by"
        targetNodeType: "identity"
        targetNodeId: "identity-uuid"
      }
    ]
  ) {
    id
  }
}
mutation {
  createEvent(
    userId: "user-uuid"
    eventType: "order.shipped"
    payload: {
      order_id: "order_12345"
      tracking_number: "1Z999AA10123456784"
    }
    originEventId: "origin-event-uuid"
  ) {
    id
    originEventId
  }
}
mutation {
  createEvent(
    userId: "user-uuid"
    eventType: "document.uploaded"
    payload: {
      document_type: "invoice"
      document_number: "INV-2024-001"
    }
    edges: [
      {
        edgeType: "attachment"
        targetNodeType: "file"
        targetNodeId: "file-uuid"
      }
    ]
  ) {
    id
  }
}

Subscribing to Custom Events#

Webhook Subscription#

In the console under Settings → Webhooks, create a subscription named Order Events pointing at https://myapp.com/webhooks/orders, matching the event type pattern order.*.

Webhook Payload#

{
  "event_id": "123e4567-e89b-12d3-a456-426614174000",
  "event_type": "order.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "app_id": "your-app-uuid",
  "data": {
    "order_id": "order_12345",
    "total": 99.99,
    "currency": "USD"
  },
  "edges": [
    {
      "edge_type": "placed_by",
      "target_node_type": "identity",
      "target_node_id": "identity-uuid"
    }
  ]
}

Use Cases#

E-commerce Integration#

# When order is placed
async def create_order_event(order, user_id, identity_id):
    await client.execute("""
        mutation CreateOrderEvent($userId: UUID!, $payload: JSON!, $identityId: UUID!) {
            createEvent(
                userId: $userId
                eventType: "order.created"
                payload: $payload
                edges: [{
                    edgeType: "placed_by"
                    targetNodeType: "identity"
                    targetNodeId: $identityId
                }]
            ) { id }
        }
    """, {
        "userId": user_id,
        "payload": {
            "order_id": order.id,
            "total": order.total,
            "items": order.items
        },
        "identityId": identity_id
    })

# When order ships
async def create_shipped_event(order, user_id, original_event_id):
    await client.execute("""
        mutation CreateShippedEvent($userId: UUID!, $payload: JSON!, $originId: UUID!) {
            createEvent(
                userId: $userId
                eventType: "order.shipped"
                payload: $payload
                originEventId: $originId
            ) { id }
        }
    """, {
        "userId": user_id,
        "payload": {
            "order_id": order.id,
            "tracking_number": order.tracking
        },
        "originId": original_event_id
    })

Support Ticketing#

async def create_ticket_events(ticket, user_id):
    # Ticket opened
    result = await client.execute("""
        mutation OpenTicket($userId: UUID!, $payload: JSON!) {
            createEvent(
                userId: $userId
                eventType: "support.ticket.opened"
                payload: $payload
            ) { id }
        }
    """, {
        "userId": user_id,
        "payload": {
            "ticket_id": ticket.id,
            "subject": ticket.subject,
            "priority": ticket.priority
        }
    })

    return result["createEvent"]["id"]

Analytics Events#

async def track_user_action(action, user_id, metadata=None):
    await client.execute("""
        mutation TrackAction($userId: UUID!, $eventType: String!, $payload: JSON!) {
            createEvent(
                userId: $userId
                eventType: $eventType
                payload: $payload
            ) { id }
        }
    """, {
        "userId": user_id,
        "eventType": f"analytics.{action}",
        "payload": {
            "action": action,
            "timestamp": datetime.utcnow().isoformat(),
            **(metadata or {})
        }
    })

Best Practices#

1. Consistent Naming#

Use a clear hierarchy: {domain}.{action} or {domain}.{subdomain}.{action}

2. Include Timestamps#

Add timestamps in payload for events with delayed processing.

Use edges to connect events to users, files, and other events.

4. Define Schemas#

Create schemas for validation and documentation.

5. Use Origin Events#

Chain related events using originEventId.

6. Keep Payloads Focused#

Include relevant data, not entire objects.