> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.fotostudio.io/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Outgoing webhooks

# Webhooks

Webhooks allow you to connect Fotostudio to external tools like **Make**, **Zapier**, **n8n** or your own backend. For each important event (new contact, paid invoice, signed contract...), Fotostudio automatically sends data to the URL of your choice.

---

## Configure a webhook

Go to **Modules → Outgoing webhooks** and click **New webhook**.

Fill in the following fields:

| Field | Description |
| ---- |
| **Label** | A name to help you identify it (e.g. "Make – invoices") |
| **Destination URL** | The HTTPS URL provided by your tool (Make, Zapier...) |
| **Events** | The events to listen for (you can check multiple) |

A **secret** is automatically generated upon creation. Copy it immediately: it won't be displayed again. It's used to verify that calls actually come from Fotostudio.

---

## Available events

| Event | Triggered when... |
| ---- |
| `contact.created` | A new contact is created |
| `contact.updated` | A contact is modified (details, info...) |
| `project.created` | A new session is created |
| `project.status_changed` | The status of a session changes |
| `project.confirmed` | A session is confirmed |
| `invoice.created` | An invoice is created |
| `invoice.paid` | An invoice is marked as paid |
| `payment.created` | A payment is recorded |
| `estimate.accepted` | A quote is accepted by the client |
| `estimate.refused` | A quote is refused by the client |
| `contract.signed` | A contract is signed |

---

## Format of sent data

Each call is a `POST` in JSON with the following structure:

```json
{
  "event": "invoice.paid",
  "timestamp": "2026-06-12T10:30:00Z",
  "data": {
    ...
  }
}
```

### HTTP headers

| Header | Value |
| ---- |
| `Content-Type` | `application/json` |
| `X-FS-Event` | Event name (e.g. `invoice.paid`) |
| `X-FS-Signature` | `sha256=<hmac>` (HMAC-SHA256 signature of the body) |
| `X-FS-Delivery-Id` | Unique delivery identifier |

### Verify the signature

The signature in `X-FS-Signature` is calculated as follows:

```
HMAC-SHA256(secret, body_json)
```

In Node.js for example:

```js
const crypto = require('crypto')
const sig = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex')
if (sig !== req.headers['x-fs-signature']) throw new Error('Invalid signature')
```

---

## Data details by event

### `contact.created` / `contact.updated`

```json
{
  "id": 123,
  "firstname": "Marie",
  "lastname": "Dupont",
  "email": "marie@example.com",
  "phone": "+33 6 12 34 56 78",
  "company": "Studio Photo",
  "street": "12 rue de la Paix",
  "zipcode": "75001",
  "city": "Paris",
  "country": "France",
  "created_at": "2026-01-15T09:00:00Z",
  "url": "https://www.fotostudio.io/app/contacts/123"
}
```

### `project.created` / `project.status_changed` / `project.confirmed`

```json
{
  "id": 456,
  "name": "Mariage Dupont",
  "start_time": "2026-07-20T14:00:00Z",
  "end_time": "2026-07-20T18:00:00Z",
  "project_type": "Mariage",
  "status": "Confirmé",
  "validated": true,
  "location": "Château de Versailles",
  "contact_id": 123,
  "created_at": "2026-01-15T09:00:00Z",
  "url": "https://www.fotostudio.io/app/projects/456"
}
```

### `invoice.created` / `invoice.paid`

```json
{
  "id": 789,
  "numero": "F2026-042",
  "title": "Mariage Dupont",
  "invoice_date": "2026-06-01",
  "total_price": 1500.00,
  "amount_to_pay": 750.00,
  "paid": false,
  "draft": false,
  "contact_id": 123,
  "project_id": 456,
  "created_at": "2026-06-01T10:00:00Z",
  "url": "https://www.fotostudio.io/app/invoices/789"
}
```

> `amount_to_pay` is the remaining balance to pay (total - payments already received).

### `payment.created`

```json
{
  "id": 101,
  "amount": 750.00,
  "payment_date": "2026-06-10",
  "payment_type": "Virement",
  "invoice_id": 789,
  "contact_id": 123,
  "created_at": "2026-06-10T14:00:00Z"
}
```

### `estimate.accepted` / `estimate.refused`

```json
{
  "id": 202,
  "numero": "D2026-018",
  "title": "Mariage Dupont",
  "total_price": 1500.00,
  "accepted": true,
  "refused": false,
  "accepted_at": "2026-06-05T16:00:00Z",
  "contact_id": 123,
  "project_id": 456,
  "created_at": "2026-05-20T09:00:00Z",
  "url": "https://www.fotostudio.io/app/estimates/202"
}
```

### `contract.signed`

```json
{
  "id": 303,
  "title": "Contrat Mariage Dupont",
  "signed_at": "2026-06-08T11:30:00Z",
  "project_id": 456,
  "contact_id": 123,
  "created_at": "2026-05-25T10:00:00Z",
  "url": "https://www.fotostudio.io/app/contracts/303"
}
```

---

## Delivery history

In each webhook's details page, the **Logs** tab displays the call history with:

* date and time
* triggered event
* status (success / failure)
* HTTP response code
* error message in case of failure

In case of failure, Fotostudio **automatically retries up to 3 times** with exponential delay. You can also manually retry a call from the logs.

---

## Using with Make

1. In Make, create a new scenario and add a **Webhooks → Custom webhook** module
2. Copy the URL generated by Make
3. In Fotostudio, create a webhook with this URL and select the desired events
4. Trigger a test event from Fotostudio (create a contact, for example)
5. Make receives the data. You can then build your automation

---

## Using with Zapier

1. In Zapier, create a new Zap and choose **Webhooks by Zapier → Catch Hook** as the trigger
2. Copy the URL provided by Zapier
3. In Fotostudio, create a webhook with this URL
4. Trigger a test event. Zapier automatically detects the data structure
5. Configure your action (Google Sheets, Notion, Slack...)

---

## FAQ

**What happens if my server is unavailable?**
Fotostudio retries 3 times with an increasing delay. After 3 attempts, the delivery is marked as failed but remains in the logs. You can relaunch it manually.

**How quickly does my webhook need to respond?**
Your endpoint must respond within **10 seconds**. Beyond that, the request is considered failed.

**Can I have multiple webhooks?**
Yes, you can create as many webhooks as you want, each with its own events and URL.

**How do I temporarily disable a webhook?**
In the list, use the toggle button to enable or disable a webhook without deleting it.