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

# Webhooks

> The Webhook API enables integrators to subscribe to specific events on the Synctera platform.

## Overview

Additionally, this API helps integrators reduce the number of requests to the Synctera platform for resource checks. For example, when a customer swipes a card, the Webhook API, if subscribed, will send a `POST` request to the predefined URL about the transaction. With webhooks, Synctera will push updates to the integrator instead of the integrator pulling updates.

### Understand Webhooks and Events

Creating a webhook defines what events the integrator wants to subscribe to via the `event_types` field. The following example shows how to subscribe to account updates and customer events. Use the request body of `POST /v0/webhooks` to create a webhook. `enabled_events` specifies that this webhook should be invoked whenever an account is updated, or when anything happens to a customer. Once such an event occurs, the Webhook API sends a `POST` request to `https://example.com`.

<CodeGroup>
  ```json JSON theme={"system"}
  {
    "enabled_events": ["ACCOUNT.UPDATED", "CUSTOMER.*"],
    "is_enabled": true,
    "url": "https://example.com"
  }
  ```
</CodeGroup>

Most events use the `<resource>.[<sub-resource>.]<action>` naming convention. For example, `ACCOUNT.UPDATED` means an account was updated. Resources can have sub-resources: `TRANSACTIONS.POSTED.CREATED` means to subscribe to changes of the sub-resource `TRANSACTION.POSTED`. You can also use a wildcard `*` for convenience purposes. For example, you can use it after a `<resource>` like `CUSTOMER.*`, which allows you to receive notifications for all the customer events without explicitly listing all of them. Note that Synctera will continuously add more events, so the webhook will automatically subscribe to any new events added to `<resource>` and send requests.

### Integration Steps

1. Create a signature secret for request validation
2. Implement a server to receive webhook requests
3. Create a webhook for events
4. Triggering events

<Steps>
  <Step title="Create a Signature Secret for Request Validation">
    To ensure that your incoming webhook requests come from Synctera, you must cryptographically validate each request as it arrives. Synctera will use a shared secret to sign each webhook request before sending it to you, and then you will use the same secret to validate requests. To create a webhook secret, call [`POST /v0/webhook_secrets`](/reference/createsecret) with an empty request body:

    <CodeGroup>
      ```bash Shell theme={"system"}
      curl \
        -X POST \
        $baseurl/v0/webhook_secrets \
        -H "Authorization: Bearer $apikey" \
        --data-binary ''
      ```
    </CodeGroup>

    Synctera will respond with the generated secret in the response:

    <CodeGroup>
      ```json JSON theme={"system"}
      {
        "secret": "{signature_secret}"
      }
      ```
    </CodeGroup>

    <Info>
      You must use the same secret to validate all incoming webhook requests.
    </Info>

    **Secret Replacement**

    You may want to rotate the secret or immediately replace it for security purposes.

    To rotate a secret:

    1. Call [`PUT /v0/webhook_secrets`](/reference/replacesecret) to deprecate the old secret and generate a new one. Set the `is_rolling_secret` field to `true` to generate the new secret without deleting the old secret right away.

    <CodeGroup>
      ```bash Shell theme={"system"}
      curl \
        -X PUT \
        $baseurl/v0/webhooks/secret \
        -H "Authorization: Bearer $apikey" \
        -H 'Content-Type: application/json' \
        --data-binary '
        {
          "is_rolling_secret": true
        }'
      ```
    </CodeGroup>

    <Info>
      The request deletes the last secret in 24 hours, so you will have time to update it.

      If you want to delete the last secret immediately without waiting for 24 hours, call [`DELETE /v0/webhook_secrets?old_secret_only=true`](/reference/revokesecret)
    </Info>
  </Step>

  <Step title="Implement a Server to Receive Webhook Requests">
    The applications that receive webhook requests should be publicly accessible so the Synctera platform can send the webhook request to the URL defined in the webhook resource.

    The schema for the webhook request is defined in the **OpenAPI spec** as `webhook_request_object`.

    **Request Headers**

    The request uses the `POST` method and contains the following headers:

    * `Synctera-Signature` - This is the signature of the request. Use the signature secret generated from step 2 to verify the request body.
    * `Request-Timestamp` - The time when Synctera's platform sent the request, as a POSIX timestamp: seconds since 1970-01-01 00:00:00 UTC.
    * `Content-Type` - Has the value `application/json`.

    **Example Request Body**

    <CodeGroup>
      ```json JSON theme={"system"}
      {
        "id": "8145af83-0423-488f-8799-1c8c0bf8b189",
        "url": "http://example.com",
        "webhook_id": "873f7f9c-3063-4098-adcf-1f3af25286f8",
        "type": "ACCOUNT.UPDATED",
        "event_time": "2022-01-25T11:45:54.485698-05:00",
        "metadata": "test webhook",
        "event_resource": "{\"id\":\"7fef9ad7-67dc-4af0-9ce2-70011131c20c\", \"status\": \"ACTIVE_OR_DISBURSED\" ... }",
        "event_resource_changed_fields": "{\"status\": \"RESTRICTED\"}"
      }
      ```
    </CodeGroup>

    * `id` - The current event ID
    * `url` - The URL that you specified in your webhook, also the endpoint you will receive this request.
    * `webhook_id` - The ID of the webhook that sends out this request
    * `type`- The event type
    * `metadata` - The same value as the `metadata` defines in the webhook
    * `event_resource` Escaped JSON string representing the `<resource>` of the event. If the type has `<sub-resource>`, then the string represents the sub-resource.
    * `event_resource_changed_fields` Escaped JSON string representing the top level fields that have been updated by the event, containing the value prior to the event. Only update event includes this field.

    **Resource JSON string example**

    * object before change: `{"a": 1, "b": 2, "c": 3, "n": {"m": 1}}`
    * object after change: `{"a": 4, "c": 3, "d": 5, "n": {"m": 1, "p": 2}}`

    `event_resource` is just the "object after change" itself.

    `event_resource_changed_fields` is `{"a": 1, "b": 2, "d": null, "n": {"m": 1}}` because:

    * `a` has value changed, value in `before-change object` is 1
    * `b` is deleted from the object, value in `before-change object` is 2
    * `c` is not changed, so it is not included in `event_resource_changed_fields`
    * `d` is added to the object, value is null because `before-change object` does not have it
    * `n` is a nested object where sub-field `p` is added. Note that the old value of the entire top level field `(n)` is included in the old value, and that added sub-fields (e.g. `p`) are not included.

    Therefore, `event_resource_changed_fields` from the original request body means the account resource has been updated. The account status was changed from `RESTRICTED` to `ACTIVE_OR_DISBURSED`.

    **Request Validation**

    The `Synctera-Signature` is generated via [HMAC](https://en.wikipedia.org/wiki/HMAC) with SHA256 hash and the signature secret as the key. The expected value of the `Synctera-Signature` is `HMAC256({request_timestamp} + '.' + {request_body}, {signature_secret_key})`. Your service should validate the header matches what you expect.

    Furthermore, `Synctera-Signature` may include two signature strings delimited by `.` during the rolling secret period, which the old and new signature secrets generate.

    <Warning>
      To prevent replay attacks, integrators should check that the request time is within 5 minutes of `now()`
    </Warning>

    See the example code below in Go to handle signature validation:

    <CodeGroup>
      ```go go theme={"system"}
      func ValidateSignature(secret string, payload []byte, reqTime string, signature string) error {
        // Parse request time
        reqTimeVal, err := strconv.ParseInt(reqTime, 10, 64)
        if err != nil {
          return err
        }

        // Generate the signature
        mac := hmac.New(sha256.New, []byte(secret))
        if _, err := mac.Write([]byte(reqTime + ".")); err != nil {
          return err
        }
        if _, err := mac.Write(payload); err != nil {
          return err
        }
        generatedSignature := hex.EncodeToString(mac.Sum(nil))

        // Verify with the signature header
        for _, curSign := range strings.Split(signature, ".") {
          if generatedSignature != curSign {
            continue
          }
          reqT := time.Unix(reqTimeVal, 0)
          if !reqT.Add(time.Minute * 5).After(time.Now()) {
            return errors.New("signature expired")
          }
          return nil
        }
        return errors.New("invalid signature")
      }
      ```
    </CodeGroup>

    **Response**

    The Webhook API expects an HTTP `200` response to indicate that the application processed the request successfully. Any `4xx` or `5xx` level code will be considered a failure on the application side. The Webhook API will retry the same request with exponential backoff until a successful response is received or 55 hours have passed. Events (successful or failed) are retained for 60 days.

    **Request timeout**

    Webhook requests will timeout after 5 seconds. Should a webhook request timeout, Synctera will automatically retry with exponential back off.
  </Step>

  <Step title="Create a Webhook Subscription for Events">
    Call the [`POST /v0/webhooks`](/reference/createwebhook) endpoint to create a webhook subscription. For example, If you have deployed your new service so it is accessible on the public Internet at `https://api.example.com/webhook`:

    <CodeGroup>
      ```bash Shell theme={"system"}
      curl \
        -X POST \
        $baseurl/v0/webhooks \
        -H "Authorization: Bearer $apikey" \
        -H 'Content-Type: application/json' \
        --data-binary '
        {
          "url": "https://api.example.com/webhook",
          "description": "random test",
          "enabled_events": ["ACCOUNT.*", "CUSTOMER.UPDATED"],
          "metadata": "nothing",
          "is_enabled": true
        }'
      ```
    </CodeGroup>

    * `url` is the endpoint that Synctera's webhook service will send requests to
    * `description` is a brief string that you use to describe the purpose of this webhook (mainly as a reminder to yourself)
    * `enabled_events` is the list of events that the webhook should subscribe to
    * `metadata` is an arbitrary string which will be included in every request body as the field `metadata`
    * `is_enabled` means the webhook should send requests for matching events; if false, events will be ignored

    Once you create the webhook, it will send requests for any newly triggered events.

    Subscribing to a wildcard event, e.g. `ACCOUNT.*`, will send all webhooks for all events that match that pattern. Note that this can include new event types added after the subscription was created.

    <Info>
      Webhook requests are not guaranteed to be real-time calls. In most cases Synctera will send a webhook immediately after an event occurs in most. However, in rare cases the delay could be 15-30 seconds on average. Your app should rely on synchronous responses for time sensitive operations.
    </Info>

    <Info>
      Webhook requests may be delivered multiple times, e.g. if Synctera does not receive a 200 response and therefore retries. Your application must handle duplicate requests, e.g. by checking the event ID.
    </Info>

    <Info>
      Similarly, webhook requests are not guaranteed to be delivered in order. If you update customer A, then customer B, then customer A again, then those three webhooks are *usually* delivered in that order. But when errors happen, all bets are off. You should *not* assume that the state of a resource included in a webhook request is the latest state. If you need the latest state, you should fetch it with an API call back to Synctera.
    </Info>
  </Step>

  <Step title="Triggering Events">
    We highly recommend testing your application to ensure it can receive the webhook request. There are several useful endpoints to test if the application is working correctly.

    * [`POST /v0/webhooks/trigger`](/reference/triggerevent) fires a mock event on the Synctera platform, which triggers all the webhooks that match the event (specified in the request body) to send out a request. This will help debug the application without having to CRUD on the actual resource. However, note that the webhook response body will **NOT** contain `event_resource` in this case.
    * [`GET /v0/webhooks/<webhook_id>/events/<event_id>`](/reference/getevent) returns the event with the history of the request sent, including those failed attempts with the response body.
    * [`POST /v0/webhooks/<webhook_id>/events/<event_id>/resend`](/reference/resendevent) will trigger it to send the webhook request with the same event again, without waiting for the next automatic retry attempt.
  </Step>
</Steps>
