{"id":18263311,"url":"https://github.com/safe-global/safe-events-service","last_synced_at":"2025-06-13T15:36:29.496Z","repository":{"id":165570109,"uuid":"639387055","full_name":"safe-global/safe-events-service","owner":"safe-global","description":"Handle Safe indexing events from Transaction Service and deliver as HTTP webhooks","archived":false,"fork":false,"pushed_at":"2025-03-28T11:37:20.000Z","size":911,"stargazers_count":10,"open_issues_count":10,"forks_count":41,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-03-28T12:31:07.801Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/safe-global.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-11T11:21:01.000Z","updated_at":"2025-03-28T11:37:23.000Z","dependencies_parsed_at":"2023-10-05T11:37:12.076Z","dependency_job_id":"2ebd20d2-4fcc-48e2-81ac-e0edf10fdac8","html_url":"https://github.com/safe-global/safe-events-service","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/safe-global%2Fsafe-events-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/safe-global%2Fsafe-events-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/safe-global%2Fsafe-events-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/safe-global%2Fsafe-events-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/safe-global","download_url":"https://codeload.github.com/safe-global/safe-events-service/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247246284,"owners_count":20907767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T11:10:44.538Z","updated_at":"2025-06-13T15:36:29.484Z","avatar_url":"https://github.com/safe-global.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/safe-global/safe-events-service/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/safe-global/safe-events-service/actions/workflows/ci.yml)\n[![Coverage Status](https://coveralls.io/repos/github/safe-global/safe-events-service/badge.svg?branch=main)](https://coveralls.io/github/safe-global/safe-events-service?branch=main)\n![Docker Image Version (latest by date)](https://img.shields.io/docker/v/safeglobal/safe-events-service?sort=date)\n![Node required Version](https://img.shields.io/badge/node.js-v22-green)\n\n# User documentation\n\n## Description\n\nHandle Safe indexing events from Transaction Service and deliver as HTTP webhooks.\nThis service should be connected to the [Safe Transaction Service](https://github.com/safe-global/safe-transaction-service):\n\n- Transaction service sends events to RabbitMQ.\n- Events service holds a database with services to send webhooks to, and some filters like `chainId` or `eventType` can be configured.\n- Events service connects to RabbitMQ and subscribes to the events. When an event matches filters for a service, a webhook is posted.\n\n![Events Service Diagram](./docs/img/events.png)\n\n## Endpoints\n\nAvailable endpoints:\n\n- /health/ -\u003e Check health for the service.\n- /admin/ -\u003e Admin panel to edit database models.\n- /events/sse/{CHECKSUMMED_SAFE_ADDRESS} -\u003e Server side events endpoint. If `SSE_AUTH_TOKEN` is defined then authentication\n  will be enabled and header `Authorization: Basic $SSE_AUTH_TOKEN` must be added to the request.\n\n## How to integrate with the service\n\nIf you want to integrate with the events service, you need to:\n- Build a REST API with an endpoint that can receive `json/application` requests (take a look at [Events Supported](#events-supported)).\n- Endpoint need to answer with:\n  - `HTTP 202` status\n  - Nothing in the body.\n  - It should answer **as soon as possible**, as events service will timeout in 2 seconds, if multiple timeouts are detected **service will stop sending requests** to your endpoint. So you should receive the event, return a HTTP response and then act upon it.\n  - Configuring HTTP Basic Auth in your endpoint is recommended so a malicious user cannot post fake events to your service.\n\n## Events supported\n\nSome parameters are common to every event:\n\n- `address`: Safe address.\n- `type`: Event type.\n- `chainId`: Chain id.\n\n### Multisig Confirmation\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"NEW_CONFIRMATION\",\n  \"owner\": \"\u003cEthereum checksummed address\u003e\",\n  \"safeTxHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### MultisigTransaction (executed)\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"EXECUTED_MULTISIG_TRANSACTION\",\n  \"safeTxHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"to\": \"\u003cEthereum checksummed address\u003e\",\n  \"data\": \"\u003c0x-prefixed-hex-string\u003e\" | null,\n  \"failed\": \"true\" | \"false\",\n  \"txHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### MultisigTransaction (proposed, not executed)\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"PENDING_MULTISIG_TRANSACTION\",\n  \"safeTxHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"to\": \"\u003cEthereum checksummed address\u003e\",\n  \"data\": \"\u003c0x-prefixed-hex-string\u003e\" | null,\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n## Multisig transaction deleted\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"DELETED_MULTISIG_TRANSACTION\",\n  \"safeTxHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### Incoming/Outgoing Ether\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"INCOMING_ETHER\" | \"OUTGOING_ETHER\",\n  \"txHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"value\": \"\u003cstringified-int\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### Incoming/Outgoing token (ERC20)\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"INCOMING_TOKEN\" | \"OUTGOING_TOKEN\",\n  \"tokenAddress\": \"\u003cEthereum checksummed address\u003e\",\n  \"txHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"value\": \"\u003cstringified-int\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### Incoming/Outgoing tokens (ERC721)\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"INCOMING_TOKEN\" | \"OUTGOING_TOKEN\",\n  \"tokenAddress\": \"\u003cEthereum checksummed address\u003e\",\n  \"txHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"tokenId\": \"\u003cstringified-int\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### Message created/confirmed\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\",\n  \"type\": \"MESSAGE_CREATED\" | \"MESSAGE_CONFIRMATION\",\n  \"messageHash\": \"\u003c0x-prefixed-hex-string\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### Reorg detected\n\n```json\n{\n  \"type\": \"REORG_DETECTED\",\n  \"blockNumber\": \"\u003cint\u003e\",\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n### Delegates add/update/delete\n\n```json\n{\n  \"address\": \"\u003cEthereum checksummed address\u003e\" | null,\n  \"type\": \"NEW_DELEGATE\" | \"UPDATED_DELEGATE\" | \"DELETED_DELEGATE\",\n  \"delegate\": \"\u003cEthereum checksummed address\u003e\",\n  \"delegator\": \"\u003cEthereum checksummed address\u003e\",\n  \"label\": \"\u003cstring\u003e\",\n  \"expiryDateSeconds\": \"\u003cint\u003e\" | null,\n  \"chainId\": \"\u003cstringified-int\u003e\"\n}\n```\n\n# FAQ\n\n## Do you have a dashboard/status page?\n\nNot currently.\n\n## Do I need to set up this service to receive the events?\n\nNo, this event is only meant to be run by companies running the [Safe Transaction Service](https://github.com/safe-global/safe-transaction-service). You need to develop your own endpoint as explained in [How to integrate with the service](#how-to-integrate-with-the-service)\n\n## Can you please share the delivery delay for the webhook?\n\nIndexing can take 1-2 minutes in the worst cases and less than 15 seconds in good cases.\n\n## Will the webhooks do retries?\n\nCurrently no, and please count on that maybe due to some network issues you can lose a webhook. We will work on resilience patterns like retrying or removing an integration if service cannot deliver webhooks for some time.\n\n## Do you plan to have a way to trigger a backfill in case our systems go down?\n\nIn case our systems go down, messages should be stored in our queue and when the systems are up resending should be restored (unless queue is overflowed because services have been done for a while and some old messages are discarded).\n\n## Is it available on all chains already?\n\nYes, and we can configure the [chains you want to get events from](https://docs.safe.global/safe-core-api/supported-networks).\n\n## What safes do we get webhooks requests for?\n\nYou get webhooks for all Safes, it currently cannot be configured.\n\n## Could you add more information to the webhook so we don’t have to query the transaction service?\n\nNo, we would like to keep webhook information minimal. Doing queries afterwards to the service is ok, but we are not planning on doing the webhooks the source of information for the service. The idea for webhooks is to remove the need for polling the services.\n\n## One thing that could be useful is a unique id for the events:\n\nhttps://github.com/safe-global/safe-events-service/issues/116\n\n## How do you handle confirmed/unconfirmed blocks and reorgs. When do you send an event? After waiting for confirmation or immediately? If a transaction is removed due to a chain reorg, would you still send the event before it is confirmed?\n\nWe don't send notifications when a reorg happens. We send the events as soon as we detect them, no waiting for confirmations. So you should always come to the API and make sure the data is what you expect. This events feature is something built for notifying so we prevent people http polling our API, but it shouldn't be taking the events as a source of trust, only as a signal to come back to the API (that's why we don't send a lot of informations in the events).\n\n# Developer documentation\n\n## Installation\n\nNode 22 LTS is required.\n\n```bash\n$ npm install\n```\n\n## Running the app\n\nDocker compose is required to run RabbitMQ and Postgres\n\n```bash\ncp .env.sample .env\n\ndocker compose up -d\n\n# development\n$ npm run start\n\n# watch mode\n$ npm run start:dev\n\n# production mode\n$ npm run start:prod\n```\n\n## Test\n\nNote: It's important that `web` is not running during tests, as it can consume messages\nand tests will fail.\n\n```bash\ncp .env.sample .env\n```\n\nSimple way:\n\n```bash\nbash ./scripts/run_tests.sh\n```\n\nManual way:\n\n```bash\ndocker compose down\ndocker compose up -d rabbitmq db db-migrations\n# unit tests\nnpm run test\n\n# e2e tests\nnpm run test:e2e\n\n# test coverage\nnpm run test:cov\n```\n\n## Creating database migrations\n\nBy default, the local dockerized migrations database will be used (test should not be used as it doesn't use migrations).\nTo use a custom database for migrations, set `MIGRATIONS_DATABASE_URL` environment variable.\n\nRemember to add the new database entities to `./src/datasources/db/database.options.ts`\n\n```bash\nbash ./scripts/db_generate_migrations.sh RELEVANT_MIGRATION_NAME\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsafe-global%2Fsafe-events-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsafe-global%2Fsafe-events-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsafe-global%2Fsafe-events-service/lists"}