> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openformat.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-time Notifications

> Learn how to integrate your application with OPENFORMAT's webhook system to receive real-time notifications for specific events.

## Overview

### Webhook Data

A webhook consists of a URL to be called and a set of events that will trigger calls.

Several endpoints are provided by the OPENFORMAT API to manage your webhooks. Documentation for these endpoints can be found at the [API Reference section](/api-reference/endpoint/webhook/post-webhook).

### HTTPS Requirement

To ensure security, all webhook URLs must use HTTPS and the server certificate has to be valid; expired or self-signed certificates will result on failed calls.

### Supported Events

Currently, only the following event is implemented:

* `transaction`: This event is triggered whenever one of your transactions is broadcasted in the blockchain. The payload for this event is the transaction receipt as created by the ehters.js library.

### Webhook Calls

The body of webhook calls will be a JSON with the following fields:

* `event`: A string with the event that triggered the call.
* `idempotency_key`: A string that contains an idempotency key for the webhook call. You should ignore this call if your application already processed a call with the same key.
* `payload`: A JSON object which will hold the relevant data for the triggered event.

Example body of a webhook call for a `transaction` event:

```json theme={null}
{
  "event": "transaction",
  "idempotency_key": "61966479-dbe6-4327-a6b7-ad7354c64c57",
  "payload": {
    "_type": "TransactionReceipt",
    "blockHash": "0xa7e8812cd44c01afa0c0ffca1a4bacebd957ed62196570f75868d513601fddf5",
    "blockNumber": 2,
    "contractAddress": null,
    "cumulativeGasUsed": "73602",
    "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    "gasPrice": "3517925438",
    "blobGasUsed": null,
    "blobGasPrice": null,
    "gasUsed": "73602",
    "hash": "0xa84276e7fb8b3fb9387d3e8bc58a96949028788d1e362f294c81818275e17c9f",
    "index": 0,
    "logs": [],
    "logsBloom": "0x00...000000",
    "status": 1,
    "to": "0x9716FB655f2A72b1FC1B4DB02B8ad20b6747442A"
  }
}
```

### Timeout

All webhook calls have a timeout of **5** seconds. Do not perform heavy duty tasks before responding the webhook call to avoid exceeding the timeout.

A good practice is to immediately respond the webhook call and delegate the processing to a separate process or thread.

### Redirects

The maximum number of redirects allowed in webhook calls is 3. More than that and the call will be considered failed.

### Status code

Beside redirects (3xx), webhook calls expect a 2xx status code response. Any other status code will be considered a failed call.

### Retry mechanism

If a webhook call fails, the OPENFORMAT API will attempt a maximum of **5** retries. The duration between retry attempts will follow an exponential backoff strategy.

If all **5** attempts fail, the webhook will be deactivated and no further calls will be executed on it until it is activated again.

### Specs

You can check the [API Reference section](/api-reference/) to see a list of each endpoint associated with the webhooks mechanism.

## Creation

You can create a webhook by calling endpoint `POST /api/v1/webhook` and passing:

* `url`: the HTTPS url of your webhook.
* `events`: the list of events that will trigger the webhook call. If this parameter is not sent all events will be used as triggers.

<CodeGroup>
  ```curl cURL theme={null}
  curl --request POST \
    --url https://api.openformat.tech/v1/webhook \
    --header 'Content-Type: application/json' \
    --header 'X-API-KEY: <api-key>' \
    --data '{
    "url": "https://api.example.com/webhook",
    "events": [
      "transaction"
    ]
  }'  
  ```

  ```json Example response theme={null}
  {
    "status": "success",
    "webhook": {
      "id": 123,
      "url": "https://api.example.com/webhook",
      "events": ["transaction"],
      "disabled": true,
      "disabled_at": "2024-01-23 01:23:45.55338+00",
      "created_at": "2024-01-23 01:23:45.55338+00"
    }
  }
  ```
</CodeGroup>

The created webhook will be disabled by default, in order to start receiving events you need to [verify](./verification.mdx) it first.

## Verification and Activation

### Activation

This same process needs to be followed for activating a webhook that has been deactivated because of call failures.

### Triggering a verification challenge

In order to start a webhook verification you need to call endpoint `POST /api/v1/webhook/:id/test` where `:id` is the id of the webhook to verify, example `/api/v1/webhook/12/test`.

This action will trigger a webhook call that needs to be responded correctly. The call will contain the following fields:

* `event`: With value `test`.
* `idempotency_key`: With an idempotency key for this call.
* `payload`: With value `null`

Headers of the request will contain a `x-openformat-signature` header with a valid signature.

Example verification request:

```http theme={null}
POST /webhook HTTP/1.1
accept: application/json, text/plain, */*
content-type: application/json
user-agent: Openformat-Webhook-Client
x-openformat-signature: dIqk7OzudIQqWhkRVsxrGi7nJjV0oDDGimDSLukdlVE=
Host: mywebhookapi.example.com
Content-Length: 88

{
    "event": "test",
    "idempotency_key": "c4eec277-8a0d-4203-a113-ac5f360e0caa",
    "payload": null
}
```

### Challenge response

In order to pass the challenge your response should have:

* A valid SSL certificate.
* A valid status code: 2xx
* Content type header: `application/json`.
* Response body will be a JSON with a single field `challenge`. This field will contain the value of the `x-openformat-signature` in the webhook call request.

Example response for the previous verification request:

```http theme={null}
HTTP 200 OK
content-type: application/json

{
    "challenge": "dIqk7OzudIQqWhkRVsxrGi7nJjV0oDDGimDSLukdlVE="
}
```

Once the verification is completed your webhook will be active and we will start sending calls to it.

## Secrets

### Generation

Webhook secrets are generated by the system and are associated with a user account when the account is created. Each user account is assigned a unique webhook secret.

This secret is used to sign all the calls that we send to your webhooks so you can confirm they come from OPENFORMAT API.

### Retrieving Current Secret

An endpoint `GET /api/v1/webhook/secret` exists to retrieve the current webhook secret associated with the user account. This endpoint returns the current secret in the response.

<CodeGroup>
  ```curl cURL theme={null}
  curl --request GET \
    --url https://api.openformat.tech/v1/webhook/secret \
    --header 'X-API-KEY: <api-key>'
  ```

  ```json Example response theme={null}
  {
    "status": "success",
    "webhook_secret": "7be555a9-be3b-4049-9361-664842821721"
  }
  ```
</CodeGroup>

### Generating New Secret

An endpoint `POST /api/v1/webhook/secret` exists to generate a new secret. This request has no body, and the secret is generated by the system and returned in the response. The new secret replaces the previous one associated with the user account.

<CodeGroup>
  ```curl cURL theme={null}
  curl --request POST \
    --url https://api.openformat.tech/v1/webhook/secret \
    --header 'X-API-KEY: <api-key>'
  ```

  ```json Example response theme={null}
  {
    "status": "success",
    "webhook_secret": "7be555a9-be3b-4049-9361-664842821721"
  }
  ```
</CodeGroup>

## Signature verification

While not mandatory, we strongly recommend you to confirm that calls received in you webhooks are coming from the OPENFORMAT API.

### Method

To confirm that a webhook call originated from the OPENFORMAT API use the following method:

* Retrieve the value of `x-openformat-signature` header. All webhook calls executed by the OPENFORMAT API contain this header.
* Retrieve the raw body of the call. This is a JSON object, meaning that the body begins with a `{` character and ends with a `}` character, inclusive.
* Compute the HMAC-SHA256 hash of the body using your webhook secret as signing key.
* Encode the result in `base64` format.
* Confirm that the encoded value is the same than `x-openformat-signature` header.

### Example

Assume the following is a request to a webhook call from the OPENFORMAT API.

```http theme={null}
POST /webhook HTTP/1.1
accept: application/json, text/plain, */*
content-type: application/json
user-agent: Openformat-Webhook-Client
x-openformat-signature: dIqk7OzudIQqWhkRVsxrGi7nJjV0oDDGimDSLukdlVE=
Host: mywebhookapi.example.com
Content-Length: 88

{"event":"test","idempotency_key":"c4eec277-8a0d-4203-a113-ac5f360e0caa","payload":null}
```

Also, assume the webhook secret is `f2ec0291-cf11-41ec-b9b6-bfaa218c745b`.

Then we have:

```python theme={null}
body = '{"event":"test","idempotency_key":"c4eec277-8a0d-4203-a113-ac5f360e0caa","payload":null}'
secret = 'f2ec0291-cf11-41ec-b9b6-bfaa218c745b'
```

and we need to compute `BASE64( HMAC-256( body, secret ) )`

In Python we could solve it this way:

```python theme={null}
import hmac
import hashlib
import base64

hmac_sha256 = hmac.new(secret.encode("UTF-8"), body.encode('UTF-8'), hashlib.sha256)
digest = hmac_sha256.digest()
signature = base64.b64encode(digest).decode('UTF-8')
print(signature)
# prints 'dIqk7OzudIQqWhkRVsxrGi7nJjV0oDDGimDSLukdlVE='
```

which matches the value of the `x-openformat-signature` header.

## Logs

Using endpoint `GET /api/v1/webhook/:id/logs` you can get the logs for calls executed for a specific webhook. In this URL `:id` is the identification of the webhook, example `123`.

<CodeGroup>
  ```curl cURL theme={null}
  curl --request GET \
    --url https://api.openformat.tech/v1/webhook/12/logs \
    --header 'X-API-KEY: <api-key>'
  ```

  ```curl cURL paginated theme={null}
  curl --request GET \
    --url https://api.openformat.tech/v1/webhook/12/logs?page=1&page_size=10 \
    --header 'X-API-KEY: <api-key>'
  ```

  ```json Example response theme={null}
  {
    "status": "success",
    "total": 2,
    "page": 1,
    "page_size": 10,
    "data": [
      {
        "payload": {
          "_type": "TransactionReceipt",
          "status": 1,
          "to": "0x9716FB655f2A72b1FC1B4DB02B8ad20b6747442A"
        },
        "webhook_id": 4,
        "event": "transaction",
        "idempotency_key": "ee1fe7b9-fd09-4175-9f48-36cbcf8c6f01",
        "url": "https://api.example.com/webhook",
        "successful": false,
        "response_code": null,
        "error_message": "name=ConnectionRefused, code=ConnectionRefused",
        "created_at": "11:45:00.321123+00"
      },
      {
        "payload": null,
        "webhook_id": 4,
        "event": "test",
        "idempotency_key": "c4eec277-8a0d-4203-a113-ac5f360e0caa",
        "url": "https://api.example.com/webhook",
        "successful": true,
        "response_code": 200,
        "error_message": null,
        "created_at": "11:40:00.321123+00"
      }
    ]
  }
  ```
</CodeGroup>

You can paginate the result using query variables `page` and `page_size`.
