> For the complete documentation index, see [llms.txt](https://sparkwall.gitbook.io/sparkwall-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sparkwall.gitbook.io/sparkwall-docs/postback-security.md).

# Postback Security

You should verify the signature received in the postback to ensure that the call comes from our servers.

Signature parameter should match MD5 of `user_id` `transaction_id` `payout` `secret_key`. You can find your `secret` in your placement page.

## Postback Examples (GET):

{% tabs %}
{% tab title="PHP" %}

```php
<?php
   $secret = "SECRET_KEY"; // Get your secret from placement settings

   $user_id = isset($_GET['user_id']) ? $_GET['user_id'] : null;
   $transaction_id = isset($_GET['transaction_id']) ? $_GET['transaction_id'] : null;
   $payout = isset($_GET['payout']) ? $_GET['payout'] : null;
   $signature = isset($_GET['signature']) ? $_GET['signature'] : null;

   // Validate Signature
   if(md5($user_id . $transaction_id . $payout . $secret) != $signature)
   {
       echo "ERROR: Signature doesn't match";
       return;
   }

   // Further processing can be done here
   echo "Signature is valid. Process the postback.";
?>
```

{% endtab %}

{% tab title="Python" %}

```python
from flask import Flask, request, jsonify
import hashlib

app = Flask(__name__)
secret = "SECRET_KEY"  # Get your secret from placement settings

@app.route('/postback', methods=['GET'])
def postback():
    user_id = request.args.get('user_id')
    transaction_id = request.args.get('transaction_id')
    payout = request.args.get('payout')
    signature = request.args.get('signature')

    # Validate Signature
    if hashlib.md5((user_id + transaction_id + payout + secret).encode()).hexdigest() != signature:
        return "ERROR: Signature doesn't match", 400

    # Further processing can be done here
    return "Signature is valid. Process the postback."

if __name__ == '__main__':
    app.run()
```

{% endtab %}

{% tab title="NodeJS" %}

```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
const port = 3000;
const secret = "SECRET_KEY"; // Get your secret from placement settings

app.get('/postback', (req, res) => {
    const { user_id, transaction_id, payout, signature } = req.query;

    // Validate Signature
    const hash = crypto.createHash('md5').update(user_id + transaction_id + payout + secret).digest('hex');
    if (hash !== signature) {
        res.status(400).send("ERROR: Signature doesn't match");
        return;
    }

    // Further processing can be done here
    res.send("Signature is valid. Process the postback.");
});

app.listen(port, () => {
    console.log(`Server is running on http://localhost:${port}`);
});
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'sinatra'
require 'digest'

secret = "SECRET_KEY" # Get your secret from placement settings

get '/postback' do
  user_id = params['user_id']
  transaction_id = params['transaction_id']
  payout = params['payout']
  signature = params['signature']

  # Validate Signature
  if Digest::MD5.hexdigest(user_id + transaction_id + payout + secret) != signature
    halt 400, "ERROR: Signature doesn't match"
  end

  # Further processing can be done here
  "Signature is valid. Process the postback."
end
```

{% endtab %}

{% tab title="Java" %}

```java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

@RestController
public class PostbackController {

    private final String secret = "SECRET_KEY"; // Get your secret from placement settings

    @GetMapping("/postback")
    public String postback(@RequestParam String user_id, @RequestParam String transaction_id,
                           @RequestParam String payout, @RequestParam String signature) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            String data = user_id + transaction_id + payout + secret;
            md.update(data.getBytes());
            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            if (!sb.toString().equals(signature)) {
                return "ERROR: Signature doesn't match";
            }
        } catch (NoSuchAlgorithmException e) {
            return "ERROR: Unable to process signature";
        }

        // Further processing can be done here
        return "Signature is valid. Process the postback.";
    }
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;

[ApiController]
[Route("postback")]
public class PostbackController : ControllerBase
{
    private readonly string secret = "SECRET_KEY"; // Get your secret from placement settings

    [HttpGet]
    public IActionResult Postback(string user_id, string transaction_id, string payout, string signature)
    {
        using (var md5 = MD5.Create())
        {
            var input = Encoding.UTF8.GetBytes(user_id + transaction_id + payout + secret);
            var hashBytes = md5.ComputeHash(input);
            var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();

            if (hash != signature)
            {
                return BadRequest("ERROR: Signature doesn't match");
            }
        }

        // Further processing can be done here
        return Ok("Signature is valid. Process the postback.");
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Don’t forget to check the transaction\_id against your database to ensure it doesn’t already exist.
{% endhint %}

## Status Code

Please return status code `200`, if you have successfully processed the postback.

### Whitelisting <a href="#whitelisting" id="whitelisting"></a>

In order to ensure the integrity of the postbacks, you can whitelist our server IP. Postbacks are exclusively sent from this IP address:

```markdown
66.29.153.108
```
